summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/chef.rb31
-rw-r--r--lib/chef/action_collection.rb275
-rw-r--r--lib/chef/api_client.rb75
-rw-r--r--lib/chef/api_client/registration.rb39
-rw-r--r--lib/chef/api_client_v1.rb100
-rw-r--r--lib/chef/application.rb308
-rw-r--r--lib/chef/application/apply.rb191
-rw-r--r--lib/chef/application/base.rb455
-rw-r--r--lib/chef/application/client.rb488
-rw-r--r--lib/chef/application/exit_code.rb128
-rw-r--r--lib/chef/application/knife.rb182
-rw-r--r--lib/chef/application/solo.rb342
-rw-r--r--lib/chef/application/windows_service.rb208
-rw-r--r--lib/chef/application/windows_service_manager.rb97
-rw-r--r--lib/chef/applications.rb8
-rw-r--r--lib/chef/attribute_allowlist.rb (renamed from lib/chef/whitelist.rb)28
-rw-r--r--lib/chef/attribute_blocklist.rb81
-rw-r--r--lib/chef/audit/audit_event_proxy.rb93
-rw-r--r--lib/chef/audit/audit_reporter.rb176
-rw-r--r--lib/chef/audit/control_group_data.rb139
-rw-r--r--lib/chef/audit/rspec_formatter.rb37
-rw-r--r--lib/chef/audit/runner.rb196
-rw-r--r--lib/chef/chef_class.rb58
-rw-r--r--lib/chef/chef_fs.rb4
-rw-r--r--lib/chef/chef_fs/chef_fs_data_store.rb182
-rw-r--r--lib/chef/chef_fs/command_line.rb83
-rw-r--r--lib/chef/chef_fs/config.rb21
-rw-r--r--lib/chef/chef_fs/data_handler/acl_data_handler.rb4
-rw-r--r--lib/chef/chef_fs/data_handler/client_data_handler.rb6
-rw-r--r--lib/chef/chef_fs/data_handler/client_key_data_handler.rb4
-rw-r--r--lib/chef/chef_fs/data_handler/container_data_handler.rb6
-rw-r--r--lib/chef/chef_fs/data_handler/cookbook_data_handler.rb6
-rw-r--r--lib/chef/chef_fs/data_handler/data_bag_item_data_handler.rb13
-rw-r--r--lib/chef/chef_fs/data_handler/data_handler_base.rb22
-rw-r--r--lib/chef/chef_fs/data_handler/environment_data_handler.rb6
-rw-r--r--lib/chef/chef_fs/data_handler/group_data_handler.rb6
-rw-r--r--lib/chef/chef_fs/data_handler/node_data_handler.rb6
-rw-r--r--lib/chef/chef_fs/data_handler/organization_data_handler.rb9
-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.rb2
-rw-r--r--lib/chef/chef_fs/data_handler/policy_data_handler.rb8
-rw-r--r--lib/chef/chef_fs/data_handler/policy_group_data_handler.rb4
-rw-r--r--lib/chef/chef_fs/data_handler/role_data_handler.rb6
-rw-r--r--lib/chef/chef_fs/data_handler/user_data_handler.rb4
-rw-r--r--lib/chef/chef_fs/file_pattern.rb34
-rw-r--r--lib/chef/chef_fs/file_system.rb17
-rw-r--r--lib/chef/chef_fs/file_system/base_fs_dir.rb6
-rw-r--r--lib/chef/chef_fs/file_system/base_fs_object.rb27
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/acl_dir.rb8
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/acl_entry.rb28
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/acls_dir.rb18
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/chef_server_root_dir.rb58
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/cookbook_artifact_dir.rb4
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/cookbook_artifacts_dir.rb22
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/cookbook_dir.rb83
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/cookbook_file.rb32
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/cookbook_subdir.rb6
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/cookbooks_acl_dir.rb4
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/cookbooks_dir.rb45
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/data_bag_dir.rb17
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/data_bag_entry.rb4
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/data_bags_dir.rb26
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/environments_dir.rb14
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/nodes_dir.rb32
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/org_entry.rb4
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/organization_invites_entry.rb24
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/organization_members_entry.rb24
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/policies_acl_dir.rb4
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/policies_dir.rb69
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/policy_group_entry.rb19
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/policy_groups_dir.rb10
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/policy_revision_entry.rb4
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/rest_list_dir.rb69
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/rest_list_entry.rb86
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/versioned_cookbook_dir.rb8
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/versioned_cookbooks_dir.rb20
-rw-r--r--lib/chef/chef_fs/file_system/exceptions.rb2
-rw-r--r--lib/chef/chef_fs/file_system/memory/memory_dir.rb6
-rw-r--r--lib/chef/chef_fs/file_system/memory/memory_file.rb4
-rw-r--r--lib/chef/chef_fs/file_system/memory/memory_root.rb2
-rw-r--r--lib/chef/chef_fs/file_system/multiplexed_dir.rb30
-rw-r--r--lib/chef/chef_fs/file_system/nonexistent_fs_object.rb6
-rw-r--r--lib/chef/chef_fs/file_system/repository/acl.rb8
-rw-r--r--lib/chef/chef_fs/file_system/repository/acls_dir.rb14
-rw-r--r--lib/chef/chef_fs/file_system/repository/acls_sub_dir.rb8
-rw-r--r--lib/chef/chef_fs/file_system/repository/base_file.rb12
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_artifact_dir.rb8
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir.rb63
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_entry.rb45
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_root_dir.rb100
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_versioned_cookbook_dir.rb9
-rw-r--r--lib/chef/chef_fs/file_system/repository/client.rb6
-rw-r--r--lib/chef/chef_fs/file_system/repository/client_key.rb6
-rw-r--r--lib/chef/chef_fs/file_system/repository/client_keys_dir.rb8
-rw-r--r--lib/chef/chef_fs/file_system/repository/client_keys_sub_dir.rb8
-rw-r--r--lib/chef/chef_fs/file_system/repository/clients_dir.rb8
-rw-r--r--lib/chef/chef_fs/file_system/repository/container.rb6
-rw-r--r--lib/chef/chef_fs/file_system/repository/containers_dir.rb8
-rw-r--r--lib/chef/chef_fs/file_system/repository/cookbook_artifacts_dir.rb6
-rw-r--r--lib/chef/chef_fs/file_system/repository/cookbooks_dir.rb10
-rw-r--r--lib/chef/chef_fs/file_system/repository/data_bag.rb6
-rw-r--r--lib/chef/chef_fs/file_system/repository/data_bag_item.rb6
-rw-r--r--lib/chef/chef_fs/file_system/repository/data_bags_dir.rb6
-rw-r--r--lib/chef/chef_fs/file_system/repository/directory.rb20
-rw-r--r--lib/chef/chef_fs/file_system/repository/environment.rb6
-rw-r--r--lib/chef/chef_fs/file_system/repository/environments_dir.rb8
-rw-r--r--lib/chef/chef_fs/file_system/repository/file_system_entry.rb26
-rw-r--r--lib/chef/chef_fs/file_system/repository/group.rb6
-rw-r--r--lib/chef/chef_fs/file_system/repository/groups_dir.rb8
-rw-r--r--lib/chef/chef_fs/file_system/repository/node.rb6
-rw-r--r--lib/chef/chef_fs/file_system/repository/nodes_dir.rb30
-rw-r--r--lib/chef/chef_fs/file_system/repository/policies_dir.rb8
-rw-r--r--lib/chef/chef_fs/file_system/repository/policy.rb6
-rw-r--r--lib/chef/chef_fs/file_system/repository/policy_group.rb6
-rw-r--r--lib/chef/chef_fs/file_system/repository/policy_groups_dir.rb8
-rw-r--r--lib/chef/chef_fs/file_system/repository/role.rb6
-rw-r--r--lib/chef/chef_fs/file_system/repository/roles_dir.rb8
-rw-r--r--lib/chef/chef_fs/file_system/repository/user.rb6
-rw-r--r--lib/chef/chef_fs/file_system/repository/users_dir.rb8
-rw-r--r--lib/chef/chef_fs/file_system/repository/versioned_cookbooks_dir.rb6
-rw-r--r--lib/chef/chef_fs/file_system_cache.rb9
-rw-r--r--lib/chef/chef_fs/knife.rb31
-rw-r--r--lib/chef/chef_fs/parallelizer.rb25
-rw-r--r--lib/chef/chef_fs/parallelizer/parallel_enumerable.rb9
-rw-r--r--lib/chef/chef_fs/path_utils.rb25
-rw-r--r--lib/chef/client.rb503
-rw-r--r--lib/chef/compliance/default_attributes.rb93
-rw-r--r--lib/chef/compliance/fetcher/automate.rb69
-rw-r--r--lib/chef/compliance/fetcher/chef_server.rb134
-rw-r--r--lib/chef/compliance/reporter/automate.rb201
-rw-r--r--lib/chef/compliance/reporter/chef_server_automate.rb94
-rw-r--r--lib/chef/compliance/reporter/compliance_enforcer.rb20
-rw-r--r--lib/chef/compliance/reporter/json_file.rb19
-rw-r--r--lib/chef/compliance/runner.rb266
-rw-r--r--lib/chef/config.rb14
-rw-r--r--lib/chef/config_fetcher.rb20
-rw-r--r--lib/chef/constants.rb2
-rw-r--r--lib/chef/cookbook/chefignore.rb38
-rw-r--r--lib/chef/cookbook/cookbook_collection.rb13
-rw-r--r--lib/chef/cookbook/cookbook_version_loader.rb290
-rw-r--r--lib/chef/cookbook/file_system_file_vendor.rb23
-rw-r--r--lib/chef/cookbook/file_vendor.rb4
-rw-r--r--lib/chef/cookbook/gem_installer.rb44
-rw-r--r--lib/chef/cookbook/manifest_v0.rb74
-rw-r--r--lib/chef/cookbook/manifest_v2.rb45
-rw-r--r--lib/chef/cookbook/metadata.rb444
-rw-r--r--lib/chef/cookbook/remote_file_vendor.rb26
-rw-r--r--lib/chef/cookbook/synchronizer.rb107
-rw-r--r--lib/chef/cookbook/syntax_check.rb40
-rw-r--r--lib/chef/cookbook_loader.rb249
-rw-r--r--lib/chef/cookbook_manifest.rb187
-rw-r--r--lib/chef/cookbook_site_streaming_uploader.rb61
-rw-r--r--lib/chef/cookbook_uploader.rb49
-rw-r--r--lib/chef/cookbook_version.rb351
-rw-r--r--lib/chef/daemon.rb8
-rw-r--r--lib/chef/data_bag.rb62
-rw-r--r--lib/chef/data_bag_item.rb72
-rw-r--r--lib/chef/data_collector.rb511
-rw-r--r--lib/chef/data_collector/config_validation.rb140
-rw-r--r--lib/chef/data_collector/error_handlers.rb116
-rw-r--r--lib/chef/data_collector/message_helpers.rb50
-rw-r--r--lib/chef/data_collector/messages.rb96
-rw-r--r--lib/chef/data_collector/messages/helpers.rb161
-rw-r--r--lib/chef/data_collector/resource_report.rb95
-rw-r--r--lib/chef/data_collector/run_end_message.rb191
-rw-r--r--lib/chef/data_collector/run_start_message.rb60
-rw-r--r--lib/chef/decorator.rb13
-rw-r--r--lib/chef/decorator/lazy.rb4
-rw-r--r--lib/chef/decorator/lazy_array.rb8
-rw-r--r--lib/chef/decorator/unchain.rb16
-rw-r--r--lib/chef/delayed_evaluator.rb2
-rw-r--r--lib/chef/deprecated.rb262
-rw-r--r--lib/chef/deprecation/mixin/template.rb48
-rw-r--r--lib/chef/deprecation/provider/cookbook_file.rb54
-rw-r--r--lib/chef/deprecation/provider/file.rb198
-rw-r--r--lib/chef/deprecation/provider/remote_directory.rb52
-rw-r--r--lib/chef/deprecation/provider/remote_file.rb85
-rw-r--r--lib/chef/deprecation/provider/template.rb63
-rw-r--r--lib/chef/deprecation/warnings.rb12
-rw-r--r--lib/chef/digester.rb15
-rw-r--r--lib/chef/dsl.rb12
-rw-r--r--lib/chef/dsl/audit.rb51
-rw-r--r--lib/chef/dsl/chef_provisioning.rb57
-rw-r--r--lib/chef/dsl/chef_vault.rb84
-rw-r--r--lib/chef/dsl/cheffish.rb2
-rw-r--r--lib/chef/dsl/core.rb52
-rw-r--r--lib/chef/dsl/data_query.rb42
-rw-r--r--lib/chef/dsl/declare_resource.rb98
-rw-r--r--lib/chef/dsl/definitions.rb2
-rw-r--r--lib/chef/dsl/include_attribute.rb12
-rw-r--r--lib/chef/dsl/include_recipe.rb14
-rw-r--r--lib/chef/dsl/method_missing.rb75
-rw-r--r--lib/chef/dsl/platform_introspection.rb103
-rw-r--r--lib/chef/dsl/powershell.rb4
-rw-r--r--lib/chef/dsl/reboot_pending.rb25
-rw-r--r--lib/chef/dsl/recipe.rb56
-rw-r--r--lib/chef/dsl/registry_helper.rb2
-rw-r--r--lib/chef/dsl/resources.rb27
-rw-r--r--lib/chef/dsl/universal.rb28
-rw-r--r--lib/chef/encrypted_data_bag_item.rb35
-rw-r--r--lib/chef/encrypted_data_bag_item/assertions.rb8
-rw-r--r--lib/chef/encrypted_data_bag_item/check_encrypted.rb7
-rw-r--r--lib/chef/encrypted_data_bag_item/decryption_failure.rb2
-rw-r--r--lib/chef/encrypted_data_bag_item/decryptor.rb97
-rw-r--r--lib/chef/encrypted_data_bag_item/encryptor.rb23
-rw-r--r--lib/chef/encrypted_data_bag_item/unacceptable_encrypted_data_bag_item_format.rb2
-rw-r--r--lib/chef/encrypted_data_bag_item/unsupported_cipher.rb2
-rw-r--r--lib/chef/encrypted_data_bag_item/unsupported_encrypted_data_bag_item_format.rb2
-rw-r--r--lib/chef/environment.rb93
-rw-r--r--lib/chef/event_dispatch/base.rb292
-rw-r--r--lib/chef/event_dispatch/dispatcher.rb66
-rw-r--r--lib/chef/event_dispatch/dsl.rb10
-rw-r--r--lib/chef/event_loggers/base.rb7
-rw-r--r--lib/chef/event_loggers/windows_eventlog.rb55
-rw-r--r--lib/chef/exceptions.rb114
-rw-r--r--lib/chef/file_access_control.rb10
-rw-r--r--lib/chef/file_access_control/unix.rb81
-rw-r--r--lib/chef/file_access_control/windows.rb54
-rw-r--r--lib/chef/file_cache.rb49
-rw-r--r--lib/chef/file_content_management/content_base.rb6
-rw-r--r--lib/chef/file_content_management/deploy.rb14
-rw-r--r--lib/chef/file_content_management/deploy/cp.rb6
-rw-r--r--lib/chef/file_content_management/deploy/mv_unix.rb19
-rw-r--r--lib/chef/file_content_management/deploy/mv_windows.rb10
-rw-r--r--lib/chef/file_content_management/tempfile.rb28
-rw-r--r--lib/chef/formatters/base.rb38
-rw-r--r--lib/chef/formatters/doc.rb156
-rw-r--r--lib/chef/formatters/error_description.rb23
-rw-r--r--lib/chef/formatters/error_inspectors.rb14
-rw-r--r--lib/chef/formatters/error_inspectors/api_error_formatting.rb139
-rw-r--r--lib/chef/formatters/error_inspectors/compile_error_inspector.rb14
-rw-r--r--lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb35
-rw-r--r--lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb6
-rw-r--r--lib/chef/formatters/error_inspectors/node_load_error_inspector.rb45
-rw-r--r--lib/chef/formatters/error_inspectors/registration_error_inspector.rb113
-rw-r--r--lib/chef/formatters/error_inspectors/resource_failure_inspector.rb25
-rw-r--r--lib/chef/formatters/error_inspectors/run_list_expansion_error_inspector.rb53
-rw-r--r--lib/chef/formatters/error_mapper.rb14
-rw-r--r--lib/chef/formatters/indentable_output_stream.rb45
-rw-r--r--lib/chef/formatters/minimal.rb76
-rw-r--r--lib/chef/guard_interpreter.rb6
-rw-r--r--lib/chef/guard_interpreter/default_guard_interpreter.rb17
-rw-r--r--lib/chef/guard_interpreter/resource_guard_interpreter.rb77
-rw-r--r--lib/chef/handler.rb17
-rw-r--r--lib/chef/handler/error_report.rb6
-rw-r--r--lib/chef/handler/json_file.rb9
-rw-r--r--lib/chef/http.rb203
-rw-r--r--lib/chef/http/api_versions.rb55
-rw-r--r--lib/chef/http/auth_credentials.rb23
-rw-r--r--lib/chef/http/authenticator.rb52
-rw-r--r--lib/chef/http/basic_client.rb47
-rw-r--r--lib/chef/http/cookie_jar.rb4
-rw-r--r--lib/chef/http/cookie_manager.rb6
-rw-r--r--lib/chef/http/decompressor.rb21
-rw-r--r--lib/chef/http/http_request.rb41
-rw-r--r--lib/chef/http/json_input.rb10
-rw-r--r--lib/chef/http/json_output.rb16
-rw-r--r--lib/chef/http/json_to_model_output.rb6
-rw-r--r--lib/chef/http/remote_request_id.rb7
-rw-r--r--lib/chef/http/simple.rb12
-rw-r--r--lib/chef/http/simple_json.rb12
-rw-r--r--lib/chef/http/socketless_chef_zero_client.rb32
-rw-r--r--lib/chef/http/ssl_policies.rb74
-rw-r--r--lib/chef/http/validate_content_length.rb24
-rw-r--r--lib/chef/json_compat.rb129
-rw-r--r--lib/chef/key.rb59
-rw-r--r--lib/chef/knife.rb289
-rw-r--r--lib/chef/knife/acl_add.rb57
-rw-r--r--lib/chef/knife/acl_base.rb183
-rw-r--r--lib/chef/knife/acl_bulk_add.rb78
-rw-r--r--lib/chef/knife/acl_bulk_remove.rb83
-rw-r--r--lib/chef/knife/acl_remove.rb62
-rw-r--r--lib/chef/knife/acl_show.rb56
-rw-r--r--lib/chef/knife/bootstrap.rb1173
-rw-r--r--lib/chef/knife/bootstrap/chef_vault_handler.rb33
-rw-r--r--lib/chef/knife/bootstrap/client_builder.rb60
-rw-r--r--lib/chef/knife/bootstrap/templates/README.md4
-rw-r--r--lib/chef/knife/bootstrap/templates/chef-full.erb54
-rw-r--r--lib/chef/knife/bootstrap/templates/windows-chef-client-msi.erb278
-rw-r--r--lib/chef/knife/bootstrap/train_connector.rb336
-rw-r--r--lib/chef/knife/client_bulk_delete.rb16
-rw-r--r--lib/chef/knife/client_create.rb44
-rw-r--r--lib/chef/knife/client_delete.rb34
-rw-r--r--lib/chef/knife/client_edit.rb9
-rw-r--r--lib/chef/knife/client_key_create.rb12
-rw-r--r--lib/chef/knife/client_key_delete.rb8
-rw-r--r--lib/chef/knife/client_key_edit.rb10
-rw-r--r--lib/chef/knife/client_key_list.rb10
-rw-r--r--lib/chef/knife/client_key_show.rb8
-rw-r--r--lib/chef/knife/client_list.rb13
-rw-r--r--lib/chef/knife/client_reregister.rb15
-rw-r--r--lib/chef/knife/client_show.rb7
-rw-r--r--lib/chef/knife/config_get.rb39
-rw-r--r--lib/chef/knife/config_get_profile.rb (renamed from lib/chef/provider/breakpoint.rb)29
-rw-r--r--lib/chef/knife/config_list.rb139
-rw-r--r--lib/chef/knife/config_list_profiles.rb37
-rw-r--r--lib/chef/knife/config_show.rb127
-rw-r--r--lib/chef/knife/config_use.rb61
-rw-r--r--lib/chef/knife/config_use_profile.rb47
-rw-r--r--lib/chef/knife/configure.rb113
-rw-r--r--lib/chef/knife/configure_client.rb6
-rw-r--r--lib/chef/knife/cookbook_bulk_delete.rb10
-rw-r--r--lib/chef/knife/cookbook_create.rb462
-rw-r--r--lib/chef/knife/cookbook_delete.rb20
-rw-r--r--lib/chef/knife/cookbook_download.rb41
-rw-r--r--lib/chef/knife/cookbook_list.rb16
-rw-r--r--lib/chef/knife/cookbook_metadata.rb29
-rw-r--r--lib/chef/knife/cookbook_metadata_from_file.rb15
-rw-r--r--lib/chef/knife/cookbook_show.rb54
-rw-r--r--lib/chef/knife/cookbook_site_download.rb116
-rw-r--r--lib/chef/knife/cookbook_site_install.rb196
-rw-r--r--lib/chef/knife/cookbook_site_list.rb65
-rw-r--r--lib/chef/knife/cookbook_site_search.rb53
-rw-r--r--lib/chef/knife/cookbook_site_share.rb170
-rw-r--r--lib/chef/knife/cookbook_site_show.rb66
-rw-r--r--lib/chef/knife/cookbook_site_unshare.rb63
-rw-r--r--lib/chef/knife/cookbook_site_vendor.rb46
-rw-r--r--lib/chef/knife/cookbook_test.rb95
-rw-r--r--lib/chef/knife/cookbook_upload.rb250
-rw-r--r--lib/chef/knife/core/bootstrap_context.rb183
-rw-r--r--lib/chef/knife/core/cookbook_scm_repo.rb18
-rw-r--r--lib/chef/knife/core/custom_manifest_loader.rb69
-rw-r--r--lib/chef/knife/core/formatting_options.rb49
-rw-r--r--lib/chef/knife/core/gem_glob_loader.rb20
-rw-r--r--lib/chef/knife/core/generic_presenter.rb76
-rw-r--r--lib/chef/knife/core/hashed_command_loader.rb15
-rw-r--r--lib/chef/knife/core/node_editor.rb6
-rw-r--r--lib/chef/knife/core/node_presenter.rb110
-rw-r--r--lib/chef/knife/core/object_loader.rb10
-rw-r--r--lib/chef/knife/core/status_presenter.rb98
-rw-r--r--lib/chef/knife/core/subcommand_loader.rb73
-rw-r--r--lib/chef/knife/core/text_formatter.rb10
-rw-r--r--lib/chef/knife/core/ui.rb136
-rw-r--r--lib/chef/knife/core/windows_bootstrap_context.rb406
-rw-r--r--lib/chef/knife/data_bag_create.rb21
-rw-r--r--lib/chef/knife/data_bag_delete.rb6
-rw-r--r--lib/chef/knife/data_bag_edit.rb14
-rw-r--r--lib/chef/knife/data_bag_from_file.rb27
-rw-r--r--lib/chef/knife/data_bag_list.rb12
-rw-r--r--lib/chef/knife/data_bag_secret_options.rb80
-rw-r--r--lib/chef/knife/data_bag_show.rb18
-rw-r--r--lib/chef/knife/delete.rb78
-rw-r--r--lib/chef/knife/deps.rb118
-rw-r--r--lib/chef/knife/diff.rb49
-rw-r--r--lib/chef/knife/download.rb66
-rw-r--r--lib/chef/knife/edit.rb34
-rw-r--r--lib/chef/knife/environment_compare.rb29
-rw-r--r--lib/chef/knife/environment_create.rb13
-rw-r--r--lib/chef/knife/environment_delete.rb7
-rw-r--r--lib/chef/knife/environment_edit.rb7
-rw-r--r--lib/chef/knife/environment_from_file.rb14
-rw-r--r--lib/chef/knife/environment_list.rb13
-rw-r--r--lib/chef/knife/environment_show.rb7
-rw-r--r--lib/chef/knife/exec.rb46
-rw-r--r--lib/chef/knife/group_add.rb55
-rw-r--r--lib/chef/knife/group_create.rb (renamed from lib/chef/knife/osc_user_show.rb)37
-rw-r--r--lib/chef/knife/group_destroy.rb53
-rw-r--r--lib/chef/knife/group_list.rb (renamed from lib/chef/resource/git.rb)35
-rw-r--r--lib/chef/knife/group_remove.rb56
-rw-r--r--lib/chef/knife/group_show.rb49
-rw-r--r--lib/chef/knife/help.rb101
-rw-r--r--lib/chef/knife/help_topics.rb4
-rw-r--r--lib/chef/knife/index_rebuild.rb133
-rw-r--r--lib/chef/knife/key_create.rb18
-rw-r--r--lib/chef/knife/key_create_base.rb26
-rw-r--r--lib/chef/knife/key_delete.rb4
-rw-r--r--lib/chef/knife/key_edit.rb20
-rw-r--r--lib/chef/knife/key_edit_base.rb32
-rw-r--r--lib/chef/knife/key_list.rb20
-rw-r--r--lib/chef/knife/key_list_base.rb20
-rw-r--r--lib/chef/knife/key_show.rb8
-rw-r--r--lib/chef/knife/list.rb83
-rw-r--r--lib/chef/knife/node_bulk_delete.rb11
-rw-r--r--lib/chef/knife/node_create.rb8
-rw-r--r--lib/chef/knife/node_delete.rb20
-rw-r--r--lib/chef/knife/node_edit.rb20
-rw-r--r--lib/chef/knife/node_environment_set.rb7
-rw-r--r--lib/chef/knife/node_from_file.rb10
-rw-r--r--lib/chef/knife/node_list.rb14
-rw-r--r--lib/chef/knife/node_policy_set.rb79
-rw-r--r--lib/chef/knife/node_run_list_add.rb28
-rw-r--r--lib/chef/knife/node_run_list_remove.rb18
-rw-r--r--lib/chef/knife/node_run_list_set.rb12
-rw-r--r--lib/chef/knife/node_show.rb31
-rw-r--r--lib/chef/knife/null.rb6
-rw-r--r--lib/chef/knife/osc_user_create.rb97
-rw-r--r--lib/chef/knife/osc_user_edit.rb58
-rw-r--r--lib/chef/knife/osc_user_reregister.rb64
-rw-r--r--lib/chef/knife/raw.rb76
-rw-r--r--lib/chef/knife/recipe_list.rb4
-rw-r--r--lib/chef/knife/rehash.rb33
-rw-r--r--lib/chef/knife/role_bulk_delete.rb11
-rw-r--r--lib/chef/knife/role_create.rb14
-rw-r--r--lib/chef/knife/role_delete.rb8
-rw-r--r--lib/chef/knife/role_edit.rb8
-rw-r--r--lib/chef/knife/role_env_run_list_add.rb23
-rw-r--r--lib/chef/knife/role_env_run_list_clear.rb8
-rw-r--r--lib/chef/knife/role_env_run_list_remove.rb14
-rw-r--r--lib/chef/knife/role_env_run_list_replace.rb11
-rw-r--r--lib/chef/knife/role_env_run_list_set.rb14
-rw-r--r--lib/chef/knife/role_from_file.rb10
-rw-r--r--lib/chef/knife/role_list.rb14
-rw-r--r--lib/chef/knife/role_run_list_add.rb23
-rw-r--r--lib/chef/knife/role_run_list_clear.rb8
-rw-r--r--lib/chef/knife/role_run_list_remove.rb13
-rw-r--r--lib/chef/knife/role_run_list_replace.rb11
-rw-r--r--lib/chef/knife/role_run_list_set.rb13
-rw-r--r--lib/chef/knife/role_show.rb9
-rw-r--r--lib/chef/knife/search.rb107
-rw-r--r--lib/chef/knife/serve.rb33
-rw-r--r--lib/chef/knife/show.rb30
-rw-r--r--lib/chef/knife/ssh.rb427
-rw-r--r--lib/chef/knife/ssl_check.rb97
-rw-r--r--lib/chef/knife/ssl_fetch.rb48
-rw-r--r--lib/chef/knife/status.rb59
-rw-r--r--lib/chef/knife/supermarket_download.rb102
-rw-r--r--lib/chef/knife/supermarket_install.rb173
-rw-r--r--lib/chef/knife/supermarket_list.rb57
-rw-r--r--lib/chef/knife/supermarket_search.rb36
-rw-r--r--lib/chef/knife/supermarket_share.rb147
-rw-r--r--lib/chef/knife/supermarket_show.rb47
-rw-r--r--lib/chef/knife/supermarket_unshare.rb44
-rw-r--r--lib/chef/knife/tag_create.rb6
-rw-r--r--lib/chef/knife/tag_delete.rb8
-rw-r--r--lib/chef/knife/tag_list.rb4
-rw-r--r--lib/chef/knife/upload.rb72
-rw-r--r--lib/chef/knife/user_create.rb133
-rw-r--r--lib/chef/knife/user_delete.rb59
-rw-r--r--lib/chef/knife/user_dissociate.rb (renamed from lib/chef/knife/osc_user_list.rb)37
-rw-r--r--lib/chef/knife/user_edit.rb49
-rw-r--r--lib/chef/knife/user_invite_add.rb (renamed from lib/chef/knife/osc_user_delete.rb)34
-rw-r--r--lib/chef/knife/user_invite_list.rb (renamed from lib/chef/audit/logger.rb)24
-rw-r--r--lib/chef/knife/user_invite_rescind.rb63
-rw-r--r--lib/chef/knife/user_key_create.rb10
-rw-r--r--lib/chef/knife/user_key_delete.rb8
-rw-r--r--lib/chef/knife/user_key_edit.rb10
-rw-r--r--lib/chef/knife/user_key_list.rb10
-rw-r--r--lib/chef/knife/user_key_show.rb8
-rw-r--r--lib/chef/knife/user_list.rb15
-rw-r--r--lib/chef/knife/user_reregister.rb58
-rw-r--r--lib/chef/knife/user_show.rb38
-rw-r--r--lib/chef/knife/xargs.rb138
-rw-r--r--lib/chef/knife/yaml_convert.rb91
-rw-r--r--lib/chef/local_mode.rb19
-rw-r--r--lib/chef/log.rb35
-rw-r--r--lib/chef/log/syslog.rb11
-rw-r--r--lib/chef/log/winevt.rb54
-rw-r--r--lib/chef/mash.rb247
-rw-r--r--lib/chef/mixin/api_version_request_handling.rb28
-rw-r--r--lib/chef/mixin/checksum.rb10
-rw-r--r--lib/chef/mixin/chef_utils_wiring.rb (renamed from lib/chef/mixin/language_include_recipe.rb)29
-rw-r--r--lib/chef/mixin/command.rb193
-rw-r--r--lib/chef/mixin/command/unix.rb220
-rw-r--r--lib/chef/mixin/command/windows.rb71
-rw-r--r--lib/chef/mixin/convert_to_class_name.rb59
-rw-r--r--lib/chef/mixin/create_path.rb30
-rw-r--r--lib/chef/mixin/deep_merge.rb71
-rw-r--r--lib/chef/mixin/default_paths.rb (renamed from lib/chef/provider/deploy/timestamped.rb)20
-rw-r--r--lib/chef/mixin/deprecation.rb48
-rw-r--r--lib/chef/mixin/enforce_ownership_and_permissions.rb4
-rw-r--r--lib/chef/mixin/file_class.rb6
-rw-r--r--lib/chef/mixin/from_file.rb15
-rw-r--r--lib/chef/mixin/get_source_from_package.rb11
-rw-r--r--lib/chef/mixin/homebrew_user.rb26
-rw-r--r--lib/chef/mixin/language.rb48
-rw-r--r--lib/chef/mixin/language_include_attribute.rb34
-rw-r--r--lib/chef/mixin/lazy_module_include.rb2
-rw-r--r--lib/chef/mixin/notifying_block.rb18
-rw-r--r--lib/chef/mixin/openssl_helper.rb448
-rw-r--r--lib/chef/mixin/params_validate.rb99
-rw-r--r--lib/chef/mixin/path_sanity.rb47
-rw-r--r--lib/chef/mixin/powershell_exec.rb128
-rw-r--r--lib/chef/mixin/powershell_out.rb29
-rw-r--r--lib/chef/mixin/powershell_type_coercions.rb44
-rw-r--r--lib/chef/mixin/properties.rb104
-rw-r--r--lib/chef/mixin/provides.rb7
-rw-r--r--lib/chef/mixin/proxified_socket.rb2
-rw-r--r--lib/chef/mixin/recipe_definition_dsl_core.rb35
-rw-r--r--lib/chef/mixin/securable.rb54
-rw-r--r--lib/chef/mixin/shell_out.rb118
-rw-r--r--lib/chef/mixin/subclass_directive.rb2
-rw-r--r--lib/chef/mixin/template.rb41
-rw-r--r--lib/chef/mixin/unformatter.rb8
-rw-r--r--lib/chef/mixin/uris.rb18
-rw-r--r--lib/chef/mixin/user_context.rb55
-rw-r--r--lib/chef/mixin/versioned_api.rb83
-rw-r--r--lib/chef/mixin/which.rb30
-rw-r--r--lib/chef/mixin/why_run.rb19
-rw-r--r--lib/chef/mixin/wide_string.rb32
-rw-r--r--lib/chef/mixin/windows_architecture_helper.rb28
-rw-r--r--lib/chef/mixin/windows_env_helper.rb22
-rw-r--r--lib/chef/mixin/xml_escape.rb30
-rw-r--r--lib/chef/mixins.rb26
-rw-r--r--lib/chef/monkey_patches/net-ssh-multi.rb141
-rw-r--r--lib/chef/monkey_patches/net_http.rb60
-rw-r--r--lib/chef/monkey_patches/webrick-utils.rb32
-rw-r--r--lib/chef/monkey_patches/win32/registry.rb34
-rw-r--r--lib/chef/monologger.rb90
-rw-r--r--lib/chef/nil_argument.rb3
-rw-r--r--lib/chef/node.rb312
-rw-r--r--lib/chef/node/attribute.rb761
-rw-r--r--lib/chef/node/attribute_collections.rb140
-rw-r--r--lib/chef/node/common_api.rb34
-rw-r--r--lib/chef/node/immutable_collections.rb220
-rw-r--r--lib/chef/node/mixin/deep_merge_cache.rb61
-rw-r--r--lib/chef/node/mixin/immutablize_array.rb184
-rw-r--r--lib/chef/node/mixin/immutablize_hash.rb171
-rw-r--r--lib/chef/node/mixin/mashy_array.rb68
-rw-r--r--lib/chef/node/mixin/state_tracking.rb96
-rw-r--r--lib/chef/node_map.rb283
-rw-r--r--lib/chef/null_logger.rb29
-rw-r--r--lib/chef/org.rb53
-rw-r--r--lib/chef/platform.rb6
-rw-r--r--lib/chef/platform/handler_map.rb40
-rw-r--r--lib/chef/platform/priority_map.rb12
-rw-r--r--lib/chef/platform/provider_handler_map.rb8
-rw-r--r--lib/chef/platform/provider_mapping.rb176
-rw-r--r--lib/chef/platform/provider_priority_map.rb4
-rw-r--r--lib/chef/platform/query_helpers.rb67
-rw-r--r--lib/chef/platform/rebooter.rb26
-rw-r--r--lib/chef/platform/resource_handler_map.rb8
-rw-r--r--lib/chef/platform/resource_priority_map.rb4
-rw-r--r--lib/chef/platform/service_helpers.rb119
-rw-r--r--lib/chef/policy_builder.rb8
-rw-r--r--lib/chef/policy_builder/dynamic.rb21
-rw-r--r--lib/chef/policy_builder/expand_node_object.rb111
-rw-r--r--lib/chef/policy_builder/policyfile.rb111
-rw-r--r--lib/chef/powershell.rb79
-rw-r--r--lib/chef/property.rb338
-rw-r--r--lib/chef/provider.rb362
-rw-r--r--lib/chef/provider/apt_repository.rb253
-rw-r--r--lib/chef/provider/apt_update.rb87
-rw-r--r--lib/chef/provider/batch.rb19
-rw-r--r--lib/chef/provider/cookbook_file.rb17
-rw-r--r--lib/chef/provider/cookbook_file/content.rb8
-rw-r--r--lib/chef/provider/cron.rb217
-rw-r--r--lib/chef/provider/cron/aix.rb15
-rw-r--r--lib/chef/provider/cron/solaris.rb4
-rw-r--r--lib/chef/provider/cron/unix.rb19
-rw-r--r--lib/chef/provider/deploy.rb476
-rw-r--r--lib/chef/provider/deploy/revision.rb109
-rw-r--r--lib/chef/provider/directory.rb88
-rw-r--r--lib/chef/provider/dsc_resource.rb91
-rw-r--r--lib/chef/provider/dsc_script.rb74
-rw-r--r--lib/chef/provider/env.rb169
-rw-r--r--lib/chef/provider/env/windows.rb72
-rw-r--r--lib/chef/provider/erl_call.rb108
-rw-r--r--lib/chef/provider/execute.rb54
-rw-r--r--lib/chef/provider/file.rb181
-rw-r--r--lib/chef/provider/file/content.rb6
-rw-r--r--lib/chef/provider/git.rb279
-rw-r--r--lib/chef/provider/group.rb119
-rw-r--r--lib/chef/provider/group/aix.rb35
-rw-r--r--lib/chef/provider/group/dscl.rb102
-rw-r--r--lib/chef/provider/group/gpasswd.rb18
-rw-r--r--lib/chef/provider/group/groupadd.rb67
-rw-r--r--lib/chef/provider/group/groupmod.rb59
-rw-r--r--lib/chef/provider/group/pw.rb61
-rw-r--r--lib/chef/provider/group/solaris.rb62
-rw-r--r--lib/chef/provider/group/suse.rb40
-rw-r--r--lib/chef/provider/group/usermod.rb34
-rw-r--r--lib/chef/provider/group/windows.rb60
-rw-r--r--lib/chef/provider/http_request.rb108
-rw-r--r--lib/chef/provider/ifconfig.rb257
-rw-r--r--lib/chef/provider/ifconfig/aix.rb58
-rw-r--r--lib/chef/provider/ifconfig/debian.rb91
-rw-r--r--lib/chef/provider/ifconfig/redhat.rb72
-rw-r--r--lib/chef/provider/launchd.rb156
-rw-r--r--lib/chef/provider/link.rb139
-rw-r--r--lib/chef/provider/log.rb57
-rw-r--r--lib/chef/provider/lwrp_base.rb24
-rw-r--r--lib/chef/provider/mdadm.rb93
-rw-r--r--lib/chef/provider/mount.rb76
-rw-r--r--lib/chef/provider/mount/aix.rb113
-rw-r--r--lib/chef/provider/mount/linux.rb67
-rw-r--r--lib/chef/provider/mount/mount.rb170
-rw-r--r--lib/chef/provider/mount/solaris.rb81
-rw-r--r--lib/chef/provider/mount/windows.rb38
-rw-r--r--lib/chef/provider/noop.rb6
-rw-r--r--lib/chef/provider/ohai.rb49
-rw-r--r--lib/chef/provider/osx_profile.rb255
-rw-r--r--lib/chef/provider/package.rb403
-rw-r--r--lib/chef/provider/package/aix.rb140
-rw-r--r--lib/chef/provider/package/apt.rb116
-rw-r--r--lib/chef/provider/package/bff.rb143
-rw-r--r--lib/chef/provider/package/cab.rb188
-rw-r--r--lib/chef/provider/package/chocolatey.rb139
-rw-r--r--lib/chef/provider/package/deb.rb131
-rw-r--r--lib/chef/provider/package/dnf.rb279
-rw-r--r--lib/chef/provider/package/dnf/dnf_helper.py186
-rw-r--r--lib/chef/provider/package/dnf/python_helper.rb220
-rw-r--r--lib/chef/provider/package/dnf/version.rb60
-rw-r--r--lib/chef/provider/package/dpkg.rb92
-rw-r--r--lib/chef/provider/package/easy_install.rb135
-rw-r--r--lib/chef/provider/package/freebsd/base.rb33
-rw-r--r--lib/chef/provider/package/freebsd/pkg.rb114
-rw-r--r--lib/chef/provider/package/freebsd/pkgng.rb36
-rw-r--r--lib/chef/provider/package/freebsd/port.rb16
-rw-r--r--lib/chef/provider/package/homebrew.rb156
-rw-r--r--lib/chef/provider/package/ips.rb50
-rw-r--r--lib/chef/provider/package/macports.rb51
-rw-r--r--lib/chef/provider/package/msu.rb166
-rw-r--r--lib/chef/provider/package/openbsd.rb65
-rw-r--r--lib/chef/provider/package/pacman.rb74
-rw-r--r--lib/chef/provider/package/paludis.rb62
-rw-r--r--lib/chef/provider/package/portage.rb98
-rw-r--r--lib/chef/provider/package/powershell.rb137
-rw-r--r--lib/chef/provider/package/rpm.rb70
-rw-r--r--lib/chef/provider/package/rubygems.rb303
-rw-r--r--lib/chef/provider/package/smartos.rb43
-rw-r--r--lib/chef/provider/package/snap.rb427
-rw-r--r--lib/chef/provider/package/solaris.rb103
-rw-r--r--lib/chef/provider/package/windows.rb139
-rw-r--r--lib/chef/provider/package/windows/exe.rb27
-rw-r--r--lib/chef/provider/package/windows/msi.rb44
-rw-r--r--lib/chef/provider/package/windows/registry_uninstall_entry.rb49
-rw-r--r--lib/chef/provider/package/yum.rb560
-rw-r--r--lib/chef/provider/package/yum/python_helper.rb228
-rw-r--r--lib/chef/provider/package/yum/rpm_utils.rb109
-rw-r--r--lib/chef/provider/package/yum/simplejson/LICENSE.txt79
-rw-r--r--lib/chef/provider/package/yum/simplejson/__init__.py318
-rw-r--r--lib/chef/provider/package/yum/simplejson/__init__.pycbin0 -> 12059 bytes
-rw-r--r--lib/chef/provider/package/yum/simplejson/decoder.py354
-rw-r--r--lib/chef/provider/package/yum/simplejson/decoder.pycbin0 -> 11088 bytes
-rw-r--r--lib/chef/provider/package/yum/simplejson/encoder.py440
-rw-r--r--lib/chef/provider/package/yum/simplejson/encoder.pycbin0 -> 13588 bytes
-rw-r--r--lib/chef/provider/package/yum/simplejson/scanner.py65
-rw-r--r--lib/chef/provider/package/yum/simplejson/scanner.pycbin0 -> 2405 bytes
-rw-r--r--lib/chef/provider/package/yum/simplejson/tool.py37
-rw-r--r--lib/chef/provider/package/yum/version.rb60
-rw-r--r--lib/chef/provider/package/yum/yum-dump.py307
-rw-r--r--lib/chef/provider/package/yum/yum_cache.rb353
-rw-r--r--lib/chef/provider/package/yum/yum_helper.py216
-rw-r--r--lib/chef/provider/package/zypper.rb192
-rw-r--r--lib/chef/provider/powershell_script.rb255
-rw-r--r--lib/chef/provider/reboot.rb70
-rw-r--r--lib/chef/provider/registry_key.rb174
-rw-r--r--lib/chef/provider/remote_directory.rb58
-rw-r--r--lib/chef/provider/remote_file.rb36
-rw-r--r--lib/chef/provider/remote_file/cache_control_data.rb18
-rw-r--r--lib/chef/provider/remote_file/content.rb23
-rw-r--r--lib/chef/provider/remote_file/fetcher.rb8
-rw-r--r--lib/chef/provider/remote_file/ftp.rb17
-rw-r--r--lib/chef/provider/remote_file/http.rb31
-rw-r--r--lib/chef/provider/remote_file/local_file.rb15
-rw-r--r--lib/chef/provider/remote_file/network_file.rb29
-rw-r--r--lib/chef/provider/remote_file/sftp.rb19
-rw-r--r--lib/chef/provider/resource_update.rb2
-rw-r--r--lib/chef/provider/route.rb399
-rw-r--r--lib/chef/provider/ruby_block.rb16
-rw-r--r--lib/chef/provider/script.rb53
-rw-r--r--lib/chef/provider/service.rb148
-rw-r--r--lib/chef/provider/service/aix.rb14
-rw-r--r--lib/chef/provider/service/aixinit.rb12
-rw-r--r--lib/chef/provider/service/arch.rb17
-rw-r--r--lib/chef/provider/service/debian.rb155
-rw-r--r--lib/chef/provider/service/freebsd.rb27
-rw-r--r--lib/chef/provider/service/gentoo.rb19
-rw-r--r--lib/chef/provider/service/init.rb17
-rw-r--r--lib/chef/provider/service/insserv.rb20
-rw-r--r--lib/chef/provider/service/invokercd.rb10
-rw-r--r--lib/chef/provider/service/macosx.rb78
-rw-r--r--lib/chef/provider/service/openbsd.rb36
-rw-r--r--lib/chef/provider/service/redhat.rb32
-rw-r--r--lib/chef/provider/service/simple.rb41
-rw-r--r--lib/chef/provider/service/solaris.rb25
-rw-r--r--lib/chef/provider/service/systemd.rb104
-rw-r--r--lib/chef/provider/service/upstart.rb102
-rw-r--r--lib/chef/provider/service/windows.rb417
-rw-r--r--lib/chef/provider/subversion.rb135
-rw-r--r--lib/chef/provider/support/yum_repo.erb19
-rw-r--r--lib/chef/provider/support/zypper_repo.erb17
-rw-r--r--lib/chef/provider/systemd_unit.rb198
-rw-r--r--lib/chef/provider/template.rb19
-rw-r--r--lib/chef/provider/template/content.rb34
-rw-r--r--lib/chef/provider/template_finder.rb14
-rw-r--r--lib/chef/provider/user.rb166
-rw-r--r--lib/chef/provider/user/aix.rb98
-rw-r--r--lib/chef/provider/user/dscl.rb328
-rw-r--r--lib/chef/provider/user/linux.rb70
-rw-r--r--lib/chef/provider/user/mac.rb680
-rw-r--r--lib/chef/provider/user/pw.rb73
-rw-r--r--lib/chef/provider/user/solaris.rb110
-rw-r--r--lib/chef/provider/user/useradd.rb164
-rw-r--r--lib/chef/provider/user/windows.rb72
-rw-r--r--lib/chef/provider/whyrun_safe_ruby_block.rb10
-rw-r--r--lib/chef/provider/windows_script.rb120
-rw-r--r--lib/chef/provider/yum_repository.rb66
-rw-r--r--lib/chef/provider/zypper_repository.rb188
-rw-r--r--lib/chef/provider_resolver.rb58
-rw-r--r--lib/chef/providers.rb234
-rw-r--r--lib/chef/pwsh.rb71
-rw-r--r--lib/chef/recipe.rb76
-rw-r--r--lib/chef/request_id.rb6
-rw-r--r--lib/chef/resource.rb718
-rw-r--r--lib/chef/resource/action_class.rb78
-rw-r--r--lib/chef/resource/alternatives.rb210
-rw-r--r--lib/chef/resource/apt_package.rb59
-rw-r--r--lib/chef/resource/apt_preference.rb148
-rw-r--r--lib/chef/resource/apt_repository.rb479
-rw-r--r--lib/chef/resource/apt_update.rb85
-rw-r--r--lib/chef/resource/archive_file.rb205
-rw-r--r--lib/chef/resource/bash.rb128
-rw-r--r--lib/chef/resource/batch.rb15
-rw-r--r--lib/chef/resource/bff_package.rb40
-rw-r--r--lib/chef/resource/breakpoint.rb78
-rw-r--r--lib/chef/resource/build_essential.rb187
-rw-r--r--lib/chef/resource/cab_package.rb81
-rw-r--r--lib/chef/resource/chef_client_config.rb313
-rw-r--r--lib/chef/resource/chef_client_cron.rb235
-rw-r--r--lib/chef/resource/chef_client_launchd.rb194
-rw-r--r--lib/chef/resource/chef_client_scheduled_task.rb216
-rw-r--r--lib/chef/resource/chef_client_systemd_timer.rb187
-rw-r--r--lib/chef/resource/chef_client_trusted_certificate.rb101
-rw-r--r--lib/chef/resource/chef_gem.rb92
-rw-r--r--lib/chef/resource/chef_handler.rb283
-rw-r--r--lib/chef/resource/chef_sleep.rb72
-rw-r--r--lib/chef/resource/chef_vault_secret.rb135
-rw-r--r--lib/chef/resource/chocolatey_config.rb102
-rw-r--r--lib/chef/resource/chocolatey_feature.rb97
-rw-r--r--lib/chef/resource/chocolatey_package.rb64
-rw-r--r--lib/chef/resource/chocolatey_source.rb148
-rw-r--r--lib/chef/resource/conditional.rb6
-rw-r--r--lib/chef/resource/cookbook_file.rb33
-rw-r--r--lib/chef/resource/cron.rb216
-rw-r--r--lib/chef/resource/cron/_cron_shared.rb99
-rw-r--r--lib/chef/resource/cron/cron.rb46
-rw-r--r--lib/chef/resource/cron/cron_d.rb188
-rw-r--r--lib/chef/resource/cron_access.rb102
-rw-r--r--lib/chef/resource/csh.rb15
-rw-r--r--lib/chef/resource/deploy.rb443
-rw-r--r--lib/chef/resource/directory.rb43
-rw-r--r--lib/chef/resource/dmg_package.rb202
-rw-r--r--lib/chef/resource/dnf_package.rb79
-rw-r--r--lib/chef/resource/dpkg_package.rb22
-rw-r--r--lib/chef/resource/dsc_resource.rb43
-rw-r--r--lib/chef/resource/dsc_script.rb84
-rw-r--r--lib/chef/resource/env.rb65
-rw-r--r--lib/chef/resource/erl_call.rb85
-rw-r--r--lib/chef/resource/execute.rb690
-rw-r--r--lib/chef/resource/file.rb58
-rw-r--r--lib/chef/resource/file/verification.rb44
-rw-r--r--lib/chef/resource/file/verification/systemd_unit.rb68
-rw-r--r--lib/chef/resource/freebsd_package.rb37
-rw-r--r--lib/chef/resource/gem_package.rb87
-rw-r--r--lib/chef/resource/group.rb90
-rw-r--r--lib/chef/resource/helpers/cron_validations.rb101
-rw-r--r--lib/chef/resource/homebrew_cask.rb104
-rw-r--r--lib/chef/resource/homebrew_package.rb47
-rw-r--r--lib/chef/resource/homebrew_tap.rb91
-rw-r--r--lib/chef/resource/homebrew_update.rb110
-rw-r--r--lib/chef/resource/hostname.rb260
-rw-r--r--lib/chef/resource/http_request.rb38
-rw-r--r--lib/chef/resource/ifconfig.rb211
-rw-r--r--lib/chef/resource/ips_package.rb24
-rw-r--r--lib/chef/resource/kernel_module.rb227
-rw-r--r--lib/chef/resource/ksh.rb15
-rw-r--r--lib/chef/resource/launchd.rb301
-rw-r--r--lib/chef/resource/link.rb97
-rw-r--r--lib/chef/resource/locale.rb184
-rw-r--r--lib/chef/resource/log.rb76
-rw-r--r--lib/chef/resource/lwrp_base.rb41
-rw-r--r--lib/chef/resource/macos_userdefaults.rb256
-rw-r--r--lib/chef/resource/macosx_service.rb34
-rw-r--r--lib/chef/resource/macports_package.rb16
-rw-r--r--lib/chef/resource/mdadm.rb149
-rw-r--r--lib/chef/resource/mount.rb192
-rw-r--r--lib/chef/resource/msu_package.rb66
-rw-r--r--lib/chef/resource/notify_group.rb74
-rw-r--r--lib/chef/resource/ohai.rb84
-rw-r--r--lib/chef/resource/ohai_hint.rb123
-rw-r--r--lib/chef/resource/openbsd_package.rb22
-rw-r--r--lib/chef/resource/openssl_dhparam.rb110
-rw-r--r--lib/chef/resource/openssl_ec_private_key.rb119
-rw-r--r--lib/chef/resource/openssl_ec_public_key.rb96
-rw-r--r--lib/chef/resource/openssl_rsa_private_key.rb115
-rw-r--r--lib/chef/resource/openssl_rsa_public_key.rb97
-rw-r--r--lib/chef/resource/openssl_x509_certificate.rb267
-rw-r--r--lib/chef/resource/openssl_x509_crl.rb152
-rw-r--r--lib/chef/resource/openssl_x509_request.rb187
-rw-r--r--lib/chef/resource/osx_profile.rb333
-rw-r--r--lib/chef/resource/package.rb41
-rw-r--r--lib/chef/resource/pacman_package.rb9
-rw-r--r--lib/chef/resource/paludis_package.rb26
-rw-r--r--lib/chef/resource/perl.rb15
-rw-r--r--lib/chef/resource/plist.rb222
-rw-r--r--lib/chef/resource/portage_package.rb25
-rw-r--r--lib/chef/resource/powershell_package.rb50
-rw-r--r--lib/chef/resource/powershell_package_source.rb171
-rw-r--r--lib/chef/resource/powershell_script.rb62
-rw-r--r--lib/chef/resource/python.rb14
-rw-r--r--lib/chef/resource/reboot.rb76
-rw-r--r--lib/chef/resource/registry_key.rb158
-rw-r--r--lib/chef/resource/remote_directory.rb113
-rw-r--r--lib/chef/resource/remote_file.rb154
-rw-r--r--lib/chef/resource/resource_notification.rb82
-rw-r--r--lib/chef/resource/rhsm_errata.rb50
-rw-r--r--lib/chef/resource/rhsm_errata_level.rb56
-rw-r--r--lib/chef/resource/rhsm_register.rb195
-rw-r--r--lib/chef/resource/rhsm_repo.rb66
-rw-r--r--lib/chef/resource/rhsm_subscription.rb99
-rw-r--r--lib/chef/resource/route.rb121
-rw-r--r--lib/chef/resource/rpm_package.rb21
-rw-r--r--lib/chef/resource/ruby.rb11
-rw-r--r--lib/chef/resource/ruby_block.rb28
-rw-r--r--lib/chef/resource/scm.rb185
-rw-r--r--lib/chef/resource/scm/_scm.rb50
-rw-r--r--lib/chef/resource/scm/git.rb144
-rw-r--r--lib/chef/resource/scm/subversion.rb73
-rw-r--r--lib/chef/resource/script.rb57
-rw-r--r--lib/chef/resource/service.rb226
-rw-r--r--lib/chef/resource/smartos_package.rb20
-rw-r--r--lib/chef/resource/snap_package.rb (renamed from lib/chef/resource/easy_install_package.rb)22
-rw-r--r--lib/chef/resource/solaris_package.rb20
-rw-r--r--lib/chef/resource/ssh_known_hosts_entry.rb164
-rw-r--r--lib/chef/resource/subversion.rb44
-rw-r--r--lib/chef/resource/sudo.rb267
-rw-r--r--lib/chef/resource/support/client.erb64
-rw-r--r--lib/chef/resource/support/cron.d.erb28
-rw-r--r--lib/chef/resource/support/cron_access.erb4
-rw-r--r--lib/chef/resource/support/ssh_known_hosts.erb3
-rw-r--r--lib/chef/resource/support/sudoer.erb17
-rw-r--r--lib/chef/resource/support/ulimit.erb41
-rw-r--r--lib/chef/resource/swap_file.rb228
-rw-r--r--lib/chef/resource/sysctl.rb233
-rw-r--r--lib/chef/resource/systemd_unit.rb104
-rw-r--r--lib/chef/resource/template.rb64
-rw-r--r--lib/chef/resource/timezone.rb178
-rw-r--r--lib/chef/resource/user.rb190
-rw-r--r--lib/chef/resource/user/aix_user.rb6
-rw-r--r--lib/chef/resource/user/dscl_user.rb12
-rw-r--r--lib/chef/resource/user/linux_user.rb15
-rw-r--r--lib/chef/resource/user/mac_user.rb122
-rw-r--r--lib/chef/resource/user/pw_user.rb6
-rw-r--r--lib/chef/resource/user/solaris_user.rb6
-rw-r--r--lib/chef/resource/user/windows_user.rb15
-rw-r--r--lib/chef/resource/user_ulimit.rb116
-rw-r--r--lib/chef/resource/whyrun_safe_ruby_block.rb2
-rw-r--r--lib/chef/resource/windows_ad_join.rb242
-rw-r--r--lib/chef/resource/windows_audit_policy.rb232
-rw-r--r--lib/chef/resource/windows_auto_run.rb99
-rw-r--r--lib/chef/resource/windows_certificate.rb356
-rw-r--r--lib/chef/resource/windows_dfs_folder.rb77
-rw-r--r--lib/chef/resource/windows_dfs_namespace.rb115
-rw-r--r--lib/chef/resource/windows_dfs_server.rb80
-rw-r--r--lib/chef/resource/windows_dns_record.rb95
-rw-r--r--lib/chef/resource/windows_dns_zone.rb84
-rw-r--r--lib/chef/resource/windows_env.rb230
-rw-r--r--lib/chef/resource/windows_feature.rb149
-rw-r--r--lib/chef/resource/windows_feature_dism.rb233
-rw-r--r--lib/chef/resource/windows_feature_powershell.rb242
-rw-r--r--lib/chef/resource/windows_firewall_profile.rb196
-rw-r--r--lib/chef/resource/windows_firewall_rule.rb326
-rw-r--r--lib/chef/resource/windows_font.rb135
-rw-r--r--lib/chef/resource/windows_package.rb152
-rw-r--r--lib/chef/resource/windows_pagefile.rb238
-rw-r--r--lib/chef/resource/windows_path.rb92
-rw-r--r--lib/chef/resource/windows_printer.rb166
-rw-r--r--lib/chef/resource/windows_printer_port.rb166
-rw-r--r--lib/chef/resource/windows_script.rb33
-rw-r--r--lib/chef/resource/windows_security_policy.rb165
-rw-r--r--lib/chef/resource/windows_service.rb230
-rw-r--r--lib/chef/resource/windows_share.rb345
-rw-r--r--lib/chef/resource/windows_shortcut.rb90
-rw-r--r--lib/chef/resource/windows_task.rb1070
-rw-r--r--lib/chef/resource/windows_uac.rb114
-rw-r--r--lib/chef/resource/windows_user_privilege.rb223
-rw-r--r--lib/chef/resource/windows_workgroup.rb130
-rw-r--r--lib/chef/resource/yum_package.rb161
-rw-r--r--lib/chef/resource/yum_repository.rb229
-rw-r--r--lib/chef/resource/zypper_package.rb49
-rw-r--r--lib/chef/resource/zypper_repository.rb116
-rw-r--r--lib/chef/resource_builder.rb81
-rw-r--r--lib/chef/resource_collection.rb41
-rw-r--r--lib/chef/resource_collection/resource_collection_serialization.rb26
-rw-r--r--lib/chef/resource_collection/resource_list.rb22
-rw-r--r--lib/chef/resource_collection/resource_set.rb89
-rw-r--r--lib/chef/resource_collection/stepable_iterator.rb4
-rw-r--r--lib/chef/resource_definition.rb16
-rw-r--r--lib/chef/resource_definition_list.rb8
-rw-r--r--lib/chef/resource_inspector.rb118
-rw-r--r--lib/chef/resource_reporter.rb189
-rw-r--r--lib/chef/resource_resolver.rb62
-rw-r--r--lib/chef/resources.rb236
-rw-r--r--lib/chef/rest.rb209
-rw-r--r--lib/chef/role.rb73
-rw-r--r--lib/chef/run_context.rb262
-rw-r--r--lib/chef/run_context/cookbook_compiler.rb164
-rw-r--r--lib/chef/run_list.rb19
-rw-r--r--lib/chef/run_list/run_list_expansion.rb37
-rw-r--r--lib/chef/run_list/run_list_item.rb16
-rw-r--r--lib/chef/run_list/versioned_recipe_list.rb36
-rw-r--r--lib/chef/run_lock.rb25
-rw-r--r--lib/chef/run_status.rb37
-rw-r--r--lib/chef/runner.rb77
-rw-r--r--lib/chef/scan_access_control.rb12
-rw-r--r--lib/chef/search/query.rb85
-rw-r--r--lib/chef/server_api.rb31
-rw-r--r--lib/chef/server_api_versions.rb63
-rw-r--r--lib/chef/shell.rb239
-rw-r--r--lib/chef/shell/ext.rb353
-rw-r--r--lib/chef/shell/model_wrapper.rb9
-rw-r--r--lib/chef/shell/shell_session.rb62
-rw-r--r--lib/chef/shell_out.rb13
-rw-r--r--lib/chef/tasks/chef_repo.rake200
-rw-r--r--lib/chef/train_transport.rb (renamed from lib/chef/resource/deploy_revision.rb)20
-rw-r--r--lib/chef/user.rb73
-rw-r--r--lib/chef/user_v1.rb113
-rw-r--r--lib/chef/util/backup.rb12
-rw-r--r--lib/chef/util/diff.rb54
-rw-r--r--lib/chef/util/dsc/configuration_generator.rb98
-rw-r--r--lib/chef/util/dsc/lcm_output_parser.rb82
-rw-r--r--lib/chef/util/dsc/local_configuration_manager.rb89
-rw-r--r--lib/chef/util/dsc/resource_store.rb22
-rw-r--r--lib/chef/util/editor.rb2
-rw-r--r--lib/chef/util/file_edit.rb35
-rw-r--r--lib/chef/util/path_helper.rb2
-rw-r--r--lib/chef/util/powershell/cmdlet.rb173
-rw-r--r--lib/chef/util/powershell/cmdlet_result.rb61
-rw-r--r--lib/chef/util/powershell/ps_credential.rb36
-rw-r--r--lib/chef/util/selinux.rb23
-rw-r--r--lib/chef/util/threaded_job_queue.rb6
-rw-r--r--lib/chef/util/windows/logon_session.rb129
-rw-r--r--lib/chef/util/windows/net_group.rb60
-rw-r--r--lib/chef/util/windows/net_use.rb32
-rw-r--r--lib/chef/util/windows/net_user.rb69
-rw-r--r--lib/chef/util/windows/volume.rb36
-rw-r--r--lib/chef/version.rb12
-rw-r--r--lib/chef/version/platform.rb20
-rw-r--r--lib/chef/version_class.rb6
-rw-r--r--lib/chef/version_constraint.rb14
-rw-r--r--lib/chef/version_constraint/platform.rb6
-rw-r--r--lib/chef/version_string.rb (renamed from lib/chef/resource/timestamped_deploy.rb)14
-rw-r--r--lib/chef/win32/api.rb34
-rw-r--r--lib/chef/win32/api/command_line_helper.rb89
-rw-r--r--lib/chef/win32/api/crypto.rb26
-rw-r--r--lib/chef/win32/api/error.rb24
-rw-r--r--lib/chef/win32/api/file.rb181
-rw-r--r--lib/chef/win32/api/installer.rb20
-rw-r--r--lib/chef/win32/api/memory.rb12
-rw-r--r--lib/chef/win32/api/net.rb221
-rw-r--r--lib/chef/win32/api/process.rb8
-rw-r--r--lib/chef/win32/api/psapi.rb6
-rw-r--r--lib/chef/win32/api/registry.rb10
-rw-r--r--lib/chef/win32/api/security.rb220
-rw-r--r--lib/chef/win32/api/synchronization.rb14
-rw-r--r--lib/chef/win32/api/system.rb18
-rw-r--r--lib/chef/win32/api/unicode.rb10
-rw-r--r--lib/chef/win32/crypto.rb12
-rw-r--r--lib/chef/win32/error.rb12
-rw-r--r--lib/chef/win32/eventlog.rb10
-rw-r--r--lib/chef/win32/file.rb53
-rw-r--r--lib/chef/win32/file/info.rb5
-rw-r--r--lib/chef/win32/file/version_info.rb43
-rw-r--r--lib/chef/win32/handle.rb10
-rw-r--r--lib/chef/win32/memory.rb10
-rw-r--r--lib/chef/win32/mutex.rb12
-rw-r--r--lib/chef/win32/net.rb36
-rw-r--r--lib/chef/win32/process.rb16
-rw-r--r--lib/chef/win32/registry.rb95
-rw-r--r--lib/chef/win32/security.rb186
-rw-r--r--lib/chef/win32/security/ace.rb10
-rw-r--r--lib/chef/win32/security/acl.rb13
-rw-r--r--lib/chef/win32/security/securable_object.rb28
-rw-r--r--lib/chef/win32/security/security_descriptor.rb14
-rw-r--r--lib/chef/win32/security/sid.rb72
-rw-r--r--lib/chef/win32/security/token.rb14
-rw-r--r--[-rwxr-xr-x]lib/chef/win32/system.rb8
-rw-r--r--lib/chef/win32/unicode.rb16
-rw-r--r--lib/chef/win32/version.rb73
-rw-r--r--lib/chef/win32_service_constants.rb143
-rw-r--r--lib/chef/workstation_config_loader.rb2
972 files changed, 51289 insertions, 29505 deletions
diff --git a/lib/chef.rb b/lib/chef.rb
index 3e161dc365..b76dab67ac 100644
--- a/lib/chef.rb
+++ b/lib/chef.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,20 +16,19 @@
# 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_relative "chef/version"
-require "chef/daemon"
+require_relative "chef/mash"
+require_relative "chef/exceptions"
+require_relative "chef/log"
+require_relative "chef/config"
+require_relative "chef/providers"
+require_relative "chef/resources"
-require "chef/run_status"
-require "chef/handler"
-require "chef/handler/json_file"
-require "chef/event_dispatch/dsl"
-require "chef/chef_class"
+require_relative "chef/daemon"
+
+require_relative "chef/run_status"
+require_relative "chef/handler"
+require_relative "chef/handler/json_file"
+require_relative "chef/event_dispatch/dsl"
+require_relative "chef/chef_class"
diff --git a/lib/chef/action_collection.rb b/lib/chef/action_collection.rb
new file mode 100644
index 0000000000..1ac47630a9
--- /dev/null
+++ b/lib/chef/action_collection.rb
@@ -0,0 +1,275 @@
+#
+# Copyright:: Copyright (c) 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_relative "event_dispatch/base"
+
+class Chef
+ class ActionCollection < EventDispatch::Base
+ include Enumerable
+ extend Forwardable
+
+ class ActionRecord
+
+ # @return [Chef::Resource] The declared resource state.
+ #
+ attr_accessor :new_resource
+
+ # @return [Chef::Resource] The current_resource object (before-state). This can be nil
+ # for non-why-run-safe resources in why-run mode, or if load_current_resource itself
+ # threw an exception (which should be considered a bug in that load_current_resource
+ # implementation, but must be handled), or for unprocessed resources.
+ attr_accessor :current_resource
+
+ # @return [Chef::Resource] the after_resource object (after-state). This can be nil for
+ # non custom-resources or resources that do not implement load_after_resource.
+ attr_accessor :after_resource
+
+ # @return [Symbol] # The action that was run (or scheduled to run in the case of "unprocessed" resources).
+ attr_accessor :action
+
+ # @return [Exception] The exception that was thrown
+ attr_accessor :exception
+
+ # @return [Hash] JSON-formatted error description from the Chef::Formatters::ErrorMapper
+ attr_accessor :error_description
+
+ # @return [Numeric] The elapsed time in seconds with machine precision
+ attr_accessor :elapsed_time
+
+ # @return [Chef::Resource::Conditional] The conditional that caused the resource to be skipped
+ attr_accessor :conditional
+
+ # The status of the resource:
+ # - updated: ran and converged
+ # - up_to_date: skipped due to idempotency
+ # - skipped: skipped due to a conditional
+ # - failed: failed with an exception
+ # - unprocessed: resources that were not touched by a run that failed
+ #
+ # @return [Symbol] status
+ #
+ attr_accessor :status
+
+ # The "nesting" level. Outer resources in recipe context are 0 here, while for every
+ # sub-resource_collection inside of a custom resource this number is incremented by 1.
+ # Resources that are fired via build-resource or manually creating and firing
+ #
+ # @return [Integer]
+ #
+ attr_accessor :nesting_level
+
+ def initialize(new_resource, action, nesting_level)
+ @new_resource = new_resource
+ @action = action
+ @nesting_level = nesting_level
+ end
+
+ # @return [Boolean] true if there was no exception
+ def success?
+ !exception
+ end
+ end
+
+ attr_reader :action_records
+ attr_reader :pending_updates
+ attr_reader :run_context
+ attr_reader :consumers
+ attr_reader :events
+
+ def initialize(events, run_context = nil, action_records = [])
+ @action_records = action_records
+ @pending_updates = []
+ @consumers = []
+ @events = events
+ @run_context = run_context
+ end
+
+ def_delegators :@action_records, :each, :last
+
+ # Allows getting at the action_records collection filtered by nesting level and status.
+ #
+ # TODO: filtering by resource type+name
+ #
+ # @return [Chef::ActionCollection]
+ #
+ def filtered_collection(max_nesting: nil, up_to_date: true, skipped: true, updated: true, failed: true, unprocessed: true)
+ subrecords = action_records.select do |rec|
+ ( max_nesting.nil? || rec.nesting_level <= max_nesting ) &&
+ ( rec.status == :up_to_date && up_to_date ||
+ rec.status == :skipped && skipped ||
+ rec.status == :updated && updated ||
+ rec.status == :failed && failed ||
+ rec.status == :unprocessed && unprocessed )
+ end
+ self.class.new(events, run_context, subrecords)
+ end
+
+ # This hook gives us the run_context immediately after it is created so that we can wire up this object to it.
+ #
+ # This also causes the action_collection_registration event to fire, all consumers that have not yet registered with the
+ # action_collection must register via this callback. This is the latest point before resources actually start to get
+ # evaluated.
+ #
+ # (see EventDispatch::Base#)
+ #
+ def cookbook_compilation_start(run_context)
+ run_context.action_collection = self
+ # fire the action_colleciton_registration hook after cookbook_compilation_start -- last chance for consumers to register
+ run_context.events.enqueue(:action_collection_registration, self)
+ @run_context = run_context
+ end
+
+ # Consumers must call register -- either directly or through the action_collection_registration hook. If
+ # nobody has registered any interest, then no action tracking will be done.
+ #
+ # @params object [Object] callers should call with `self`
+ #
+ def register(object)
+ consumers << object
+ end
+
+ # End of an unsuccessful converge used to fire off detect_unprocessed_resources.
+ #
+ # (see EventDispatch::Base#)
+ #
+ def converge_failed(exception)
+ return if consumers.empty?
+
+ detect_unprocessed_resources
+ end
+
+ # Hook to start processing a resource. May be called within processing of an outer resource
+ # so the pending_updates array forms a stack that sub-resources are popped onto and off of.
+ # This is always called.
+ #
+ # (see EventDispatch::Base#)
+ #
+ def resource_action_start(new_resource, action, notification_type = nil, notifier = nil)
+ return if consumers.empty?
+
+ pending_updates << ActionRecord.new(new_resource, action, pending_updates.length)
+ end
+
+ # Hook called after a current resource is loaded. If load_current_resource fails, this hook will
+ # not be called and current_resource will be nil, and the resource_failed hook will be called.
+ #
+ # (see EventDispatch::Base#)
+ #
+ def resource_current_state_loaded(new_resource, action, current_resource)
+ return if consumers.empty?
+
+ current_record.current_resource = current_resource
+ end
+
+ # Hook called after an after resource is loaded. If load_after_resource fails, this hook will
+ # not be called and after_resource will be nil, and the resource_failed hook will be called.
+ #
+ # (see EventDispatch::Base#)
+ #
+ def resource_after_state_loaded(new_resource, action, after_resource)
+ return if consumers.empty?
+
+ current_record.after_resource = after_resource
+ end
+
+ # Hook called after an action is determined to be up to date.
+ #
+ # (see EventDispatch::Base#)
+ #
+ def resource_up_to_date(new_resource, action)
+ return if consumers.empty?
+
+ current_record.status = :up_to_date
+ end
+
+ # Hook called after an action is determined to be skipped due to a conditional.
+ #
+ # (see EventDispatch::Base#)
+ #
+ def resource_skipped(resource, action, conditional)
+ return if consumers.empty?
+
+ current_record.status = :skipped
+ current_record.conditional = conditional
+ end
+
+ # Hook called after an action modifies the system and is marked updated.
+ #
+ # (see EventDispatch::Base#)
+ #
+ def resource_updated(new_resource, action)
+ return if consumers.empty?
+
+ current_record.status = :updated
+ end
+
+ # Hook called after an action fails.
+ #
+ # (see EventDispatch::Base#)
+ #
+ def resource_failed(new_resource, action, exception)
+ return if consumers.empty?
+
+ current_record.status = :failed
+ current_record.exception = exception
+ current_record.error_description = Formatters::ErrorMapper.resource_failed(new_resource, action, exception).for_json
+ end
+
+ # Hook called after an action is completed. This is always called, even if the action fails.
+ #
+ # (see EventDispatch::Base#)
+ #
+ def resource_completed(new_resource)
+ return if consumers.empty?
+
+ current_record.elapsed_time = new_resource.elapsed_time
+
+ # Verify if the resource has sensitive data and create a new blank resource with only
+ # the name so we can report it back without sensitive data
+ # XXX?: what about sensitive data in the current_resource?
+ # FIXME: this needs to be display-logic
+ if current_record.new_resource.sensitive
+ klass = current_record.new_resource.class
+ resource_name = current_record.new_resource.name
+ current_record.new_resource = klass.new(resource_name)
+ end
+
+ action_records << pending_updates.pop
+ end
+
+ private
+
+ # @return [Chef::ActionCollection::ActionRecord] the current record we are working on at the top of the stack
+ def current_record
+ pending_updates[-1]
+ end
+
+ # If the chef-client run fails in the middle, we are left with a half-completed resource_collection, this
+ # method is responsible for adding all of the resources which have not yet been touched. They are marked
+ # as being "unprocessed".
+ #
+ def detect_unprocessed_resources
+ run_context.resource_collection.all_resources.select { |resource| resource.executed_by_runner == false }.each do |resource|
+ Array(resource.action).each do |action|
+ record = ActionRecord.new(resource, action, 0)
+ record.status = :unprocessed
+ action_records << record
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/api_client.rb b/lib/chef/api_client.rb
index 3c1ef7a2b6..75eee6883c 100644
--- a/lib/chef/api_client.rb
+++ b/lib/chef/api_client.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Nuo Yan (<nuo@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,13 +17,13 @@
# 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/server_api"
+require_relative "config"
+require_relative "mixin/params_validate"
+require_relative "mixin/from_file"
+require_relative "mash"
+require_relative "json_compat"
+require_relative "search/query"
+require_relative "server_api"
# DEPRECATION NOTE
#
@@ -47,62 +47,62 @@ class Chef
# Gets or sets the client name.
#
- # @params [Optional String] The name must be alpha-numeric plus - and _.
+ # @param [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:]_\.]+$/
+ regex: /^[\-[:alnum:]_\.]+$/
)
end
# Gets or sets whether this client is an admin.
#
- # @params [Optional True/False] Should be true or false - default is false.
+ # @param [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 ]
+ kind_of: [ TrueClass, FalseClass ]
)
end
# Gets or sets the public key.
#
- # @params [Optional String] The string representation of the public key.
+ # @param [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
+ kind_of: String
)
end
# Gets or sets whether this client is a validator.
#
- # @params [Boolean] whether or not the client is a validator. If
+ # @param [Boolean] arg 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]
+ kind_of: [TrueClass, FalseClass]
)
end
# Gets or sets the private key.
#
- # @params [Optional String] The string representation of the private key.
+ # @param [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, FalseClass]
+ kind_of: [String, FalseClass]
)
end
@@ -110,7 +110,7 @@ class Chef
# Private key is included if available.
#
# @return [Hash]
- def to_hash
+ def to_h
result = {
"name" => @name,
"public_key" => @public_key,
@@ -123,11 +123,13 @@ class Chef
result
end
+ alias_method :to_hash, :to_h
+
# The JSON representation of the object.
#
# @return [String] the JSON string.
def to_json(*a)
- Chef::JSONCompat.to_json(to_hash, *a)
+ Chef::JSONCompat.to_json(to_h, *a)
end
def self.from_hash(o)
@@ -140,17 +142,12 @@ class Chef
client
end
- def self.json_create(data)
- Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::ApiClient#from_hash")
- from_hash(data)
- end
-
def self.from_json(j)
from_hash(Chef::JSONCompat.parse(j))
end
def self.http_api
- Chef::ServerAPI.new(Chef::Config[:chef_server_url], { :api_version => "0" })
+ Chef::ServerAPI.new(Chef::Config[:chef_server_url], { api_version: "0" })
end
def self.reregister(name)
@@ -160,9 +157,9 @@ class Chef
def self.list(inflate = false)
if inflate
- response = Hash.new
+ response = {}
Chef::Search::Query.new.search(:client) do |n|
- n = self.json_create(n) if n.instance_of?(Hash)
+ n = json_create(n) if n.instance_of?(Hash)
response[n.name] = n
end
response
@@ -174,7 +171,7 @@ class Chef
# Load a client by name via the API
def self.load(name)
response = http_api.get("clients/#{name}")
- if response.kind_of?(Chef::ApiClient)
+ if response.is_a?(Chef::ApiClient)
response
else
from_hash(response)
@@ -188,20 +185,18 @@ class Chef
# Save this client via the REST API, returns a hash including the private key
def save
- begin
- http_api.put("clients/#{name}", { :name => self.name, :admin => self.admin, :validator => self.validator })
- rescue Net::HTTPServerException => e
- # If that fails, go ahead and try and update it
- if e.response.code == "404"
- http_api.post("clients", { :name => self.name, :admin => self.admin, :validator => self.validator })
- else
- raise e
- end
+ http_api.put("clients/#{name}", { name: name, admin: admin, validator: validator })
+ rescue Net::HTTPClientException => e
+ # If that fails, go ahead and try and update it
+ if e.response.code == "404"
+ http_api.post("clients", { name: name, admin: admin, validator: validator })
+ else
+ raise e
end
end
def reregister
- reregistered_self = http_api.put("clients/#{name}", { :name => name, :admin => admin, :validator => validator, :private_key => true })
+ reregistered_self = http_api.put("clients/#{name}", { name: name, admin: admin, validator: validator, private_key: true })
if reregistered_self.respond_to?(:[])
private_key(reregistered_self["private_key"])
else
@@ -226,7 +221,7 @@ class Chef
end
def http_api
- @http_api ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], { :api_version => "0" })
+ @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 e8ab0149e8..b05a2852a8 100644
--- a/lib/chef/api_client/registration.rb
+++ b/lib/chef/api_client/registration.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,10 @@
# limitations under the License.
#
-require "chef/config"
-require "chef/server_api"
-require "chef/exceptions"
+require_relative "../config"
+require_relative "../server_api"
+require_relative "../exceptions"
+require "fileutils" unless defined?(FileUtils)
class Chef
class ApiClient
@@ -59,6 +60,7 @@ class Chef
rescue Net::HTTPFatalError => e
# HTTPFatalError implies 5xx.
raise if retries <= 0
+
retries -= 1
Chef::Log.warn("Failed to register new client, #{retries} tries remaining")
Chef::Log.warn("Response: HTTP #{e.response.code} - #{e}")
@@ -69,8 +71,15 @@ class Chef
end
def assert_destination_writable!
- if (File.exists?(destination) && !File.writable?(destination)) || !File.writable?(File.dirname(destination))
- abs_path = File.expand_path(destination)
+ abs_path = File.expand_path(destination)
+ unless File.exist?(File.dirname(abs_path))
+ begin
+ FileUtils.mkdir_p(File.dirname(abs_path))
+ rescue Errno::EACCES
+ raise Chef::Exceptions::CannotWritePrivateKey, "I can't create the configuration directory at #{File.dirname(abs_path)} - check permissions?"
+ end
+ end
+ if (File.exist?(abs_path) && !File.writable?(abs_path)) || !File.writable?(File.dirname(abs_path))
raise Chef::Exceptions::CannotWritePrivateKey, "I can't write your private key to #{abs_path} - check permissions?"
end
end
@@ -85,10 +94,11 @@ class Chef
def create_or_update
create
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => e
# If create fails because the client exists, attempt to update. This
# requires admin privileges.
raise unless e.response.code == "409"
+
update
end
@@ -131,7 +141,7 @@ class Chef
end
def put_data
- base_put_data = { :name => name, :admin => false }
+ base_put_data = { name: name, admin: false }
if self_generate_keys?
base_put_data[:public_key] = generated_public_key
else
@@ -141,19 +151,18 @@ class Chef
end
def post_data
- post_data = { :name => name, :admin => false }
+ post_data = { name: name, admin: false }
post_data[:public_key] = generated_public_key if self_generate_keys?
post_data
end
def http_api
@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],
- }
- )
+ {
+ 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
index 47b0cd1c53..6178cb91c3 100644
--- a/lib/chef/api_client_v1.rb
+++ b/lib/chef/api_client_v1.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Nuo Yan (<nuo@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,16 +17,16 @@
# 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"
+require_relative "config"
+require_relative "mixin/params_validate"
+require_relative "mixin/from_file"
+require_relative "mash"
+require_relative "json_compat"
+require_relative "search/query"
+require_relative "exceptions"
+require_relative "mixin/api_version_request_handling"
+require_relative "server_api"
+require_relative "api_client"
# COMPATIBILITY NOTE
#
@@ -44,7 +44,7 @@ class Chef
include Chef::Mixin::ParamsValidate
include Chef::Mixin::ApiVersionRequestHandling
- SUPPORTED_API_VERSIONS = [0, 1]
+ SUPPORTED_API_VERSIONS = [0, 1].freeze
# Create a new Chef::ApiClientV1 object.
def initialize
@@ -57,88 +57,88 @@ class Chef
end
def chef_rest_v0
- @chef_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], { :api_version => "0", :inflate_json_class => false })
+ @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 })
+ @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 })
+ 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 _.
+ # @param arg [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:]_\.]+$/
+ regex: /^[\-[:alnum:]_\.]+$/
)
end
# Gets or sets whether this client is an admin.
#
- # @params [Optional True/False] Should be true or false - default is false.
+ # @param arg [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 ]
+ kind_of: [ TrueClass, FalseClass ]
)
end
# Gets or sets the public key.
#
- # @params [Optional String] The string representation of the public key.
+ # @param arg [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
+ kind_of: String
)
end
# Gets or sets whether this client is a validator.
#
- # @params [Boolean] whether or not the client is a validator. If
+ # @param arg [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]
+ 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.
+ # @param arg [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]
+ 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.
+ # @param arg [Optional True/False] Should be true or false - default is false.
# @return [True/False] The current value
def create_key(arg = nil)
set_or_return(
:create_key,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ kind_of: [ TrueClass, FalseClass ]
)
end
@@ -146,7 +146,7 @@ class Chef
# Private key is included if available.
#
# @return [Hash]
- def to_hash
+ def to_h
result = {
"name" => @name,
"validator" => @validator,
@@ -159,11 +159,13 @@ class Chef
result
end
+ alias_method :to_hash, :to_h
+
# The JSON representation of the object.
#
# @return [String] the JSON string.
def to_json(*a)
- Chef::JSONCompat.to_json(to_hash, *a)
+ Chef::JSONCompat.to_json(to_h, *a)
end
def self.from_hash(o)
@@ -188,9 +190,9 @@ class Chef
def self.list(inflate = false)
if inflate
- response = Hash.new
+ response = {}
Chef::Search::Query.new.search(:client) do |n|
- n = self.from_hash(n) if n.instance_of?(Hash)
+ n = from_hash(n) if n.instance_of?(Hash)
response[n.name] = n
end
response
@@ -200,6 +202,7 @@ class Chef
end
# Load a client by name via the API
+ # @param name [String] the client name
def self.load(name)
response = http_api.get("clients/#{name}")
Chef::ApiClientV1.from_hash(response)
@@ -212,29 +215,27 @@ class Chef
# 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
+ update
+ rescue Net::HTTPClientException => e
+ # If that fails, go ahead and try and update it
+ if e.response.code == "404"
+ create
+ else
+ raise e
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 })
+ 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
+ rescue Net::HTTPClientException => 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"]
@@ -255,7 +256,7 @@ class Chef
# 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 = { name: name }
payload[:validator] = validator unless validator.nil?
# DEPRECATION
@@ -265,10 +266,11 @@ class Chef
begin
new_client = chef_rest_v1.put("clients/#{name}", payload)
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => 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
@@ -278,11 +280,11 @@ class Chef
# Create the client via the REST API
def create
payload = {
- :name => name,
- :validator => validator,
+ 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,
+ admin: admin,
}
begin
# try API V1
@@ -302,7 +304,7 @@ class Chef
new_client.delete("chef_key")
end
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => 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)
@@ -313,7 +315,7 @@ class Chef
new_client = chef_rest_v0.post("clients", payload)
end
- Chef::ApiClientV1.from_hash(self.to_hash.merge(new_client))
+ Chef::ApiClientV1.from_hash(to_h.merge(new_client))
end
# As a string
diff --git a/lib/chef/application.rb b/lib/chef/application.rb
index f9735a3769..117f498831 100644
--- a/lib/chef/application.rb
+++ b/lib/chef/application.rb
@@ -1,7 +1,7 @@
#
# Author:: AJ Christensen (<aj@chef.io>)
# Author:: Mark Mzyk (mmzyk@chef.io)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,19 +16,21 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-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"
-require "chef/application/exit_code"
-require "yaml"
+require "pp" unless defined?(PP)
+require "socket" unless defined?(Socket)
+require_relative "config"
+require_relative "exceptions"
+require_relative "local_mode"
+require_relative "log"
+require_relative "platform"
+require "mixlib/cli" unless defined?(Mixlib::CLI)
+require "tmpdir" unless defined?(Dir.mktmpdir)
+require "rbconfig" unless defined?(RbConfig)
+require_relative "application/exit_code"
+require "chef-utils" unless defined?(ChefUtils::CANARY)
+module LicenseAcceptance
+ autoload :Acceptor, "license_acceptance/acceptor"
+end
class Chef
class Application
@@ -39,13 +41,17 @@ class Chef
@chef_client = nil
@chef_client_json = nil
+ end
- # Always switch to a readable directory. Keeps subsequent Dir.chdir() {}
- # from failing due to permissions when launched as a less privileged user.
+ # Configure mixlib-cli to always separate defaults from user-supplied CLI options
+ def self.use_separate_defaults?
+ true
end
# Reconfigure the application. You'll want to override and super this method.
def reconfigure
+ # In case any gems were installed for use in the config.
+ Gem.clear_paths
configure_chef
configure_logging
configure_encoding
@@ -53,10 +59,11 @@ class Chef
end
# Get this party started
- def run
+ def run(enforce_license: false)
setup_signal_handlers
reconfigure
setup_application
+ check_license_acceptance if enforce_license
run_application
end
@@ -69,30 +76,61 @@ class Chef
Chef::Application.fatal!("SIGTERM received, stopping", Chef::Exceptions::SigTerm.new)
end
- unless Chef::Platform.windows?
+ unless ChefUtils.windows?
trap("QUIT") do
- Chef::Log.info("SIGQUIT received, call stack:\n " + caller.join("\n "))
+ logger.info("SIGQUIT received, call stack:\n " + caller.join("\n "))
end
trap("HUP") do
- Chef::Log.info("SIGHUP received, reconfiguring")
+ logger.info("SIGHUP received, reconfiguring")
reconfigure
end
end
end
+ def emit_warnings
+ logger.warn "chef_config[:zypper_check_gpg] is set to false which disables security checking on zypper packages" unless chef_config[:zypper_check_gpg]
+ end
+
# Parse configuration (options and config file)
def configure_chef
parse_options
- load_config_file
- Chef::Config.export_proxies
- Chef::Config.init_openssl
+ begin
+ load_config_file
+ rescue Exception => e
+ Chef::Application.fatal!(e.message, Chef::Exceptions::ConfigurationError.new)
+ end
+ chef_config.export_proxies
+ chef_config.init_openssl
+ File.umask chef_config[:umask]
+ end
+
+ # @api private (test injection)
+ def chef_config
+ Chef::Config
+ end
+
+ # @api private (test injection)
+ def logger
+ Chef::Log
+ end
+
+ def self.logger
+ Chef::Log
+ end
+
+ # @api private (test injection)
+ def chef_configfetcher
+ require_relative "config_fetcher"
+ Chef::ConfigFetcher
end
# Parse the config file
def load_config_file
- config_fetcher = Chef::ConfigFetcher.new(config[:config_file])
+ # apply the default cli options first
+ chef_config.merge!(default_config)
+ config_fetcher = chef_configfetcher.new(config[:config_file])
# Some config settings are derived relative to the config file path; if
# given as a relative path, this is computed relative to cwd, but
# chef-client will later chdir to root, so we need to get the absolute path
@@ -100,128 +138,102 @@ class Chef
config[:config_file] = config_fetcher.expanded_path
if config[:config_file].nil?
- Chef::Log.warn("No config file found or specified on command line, using command line options.")
+ logger.warn("No config file found or specified on command line. Using command line options instead.")
elsif config_fetcher.config_missing?
- Chef::Log.warn("*****************************************")
- Chef::Log.warn("Did not find config file: #{config[:config_file]}, using command line options.")
- Chef::Log.warn("*****************************************")
+ logger.warn("*****************************************")
+ logger.warn("Did not find config file: #{config[:config_file]}. Using command line options instead.")
+ logger.warn("*****************************************")
else
config_content = config_fetcher.read_config
apply_config(config_content, config[:config_file])
end
extra_config_options = config.delete(:config_option)
- Chef::Config.merge!(config)
- if extra_config_options
- extra_parsed_options = extra_config_options.inject({}) do |memo, option|
- # Sanity check value.
- Chef::Application.fatal!("Unparsable config option #{option.inspect}") if option.empty? || !option.include?("=")
- # Split including whitespace if someone does truly odd like
- # --config-option "foo = bar"
- key, value = option.split(/\s*=\s*/, 2)
- # Call to_sym because Chef::Config expects only symbol keys. Also
- # runs a simple parse on the string for some common types.
- memo[key.to_sym] = YAML.safe_load(value)
- memo
- end
- Chef::Config.merge!(extra_parsed_options)
- end
+ chef_config.merge!(config)
+ apply_extra_config_options(extra_config_options)
end
+ def apply_extra_config_options(extra_config_options)
+ chef_config.apply_extra_config_options(extra_config_options)
+ end
+
+ # Set the specific recipes to Chef::Config if the recipes are valid
+ # otherwise log a fatal error message and exit the application.
def set_specific_recipes
- Chef::Config[:specific_recipes] =
- cli_arguments.map { |file| File.expand_path(file) } if
- cli_arguments.respond_to?(:map)
+ if cli_arguments.is_a?(Array) &&
+ (cli_arguments.empty? || cli_arguments.all? { |file| File.file?(file) } )
+ chef_config[:specific_recipes] =
+ cli_arguments.map { |file| File.expand_path(file) }
+ else
+ Chef::Application.fatal!("Invalid argument; could not find the following recipe files: \"" +
+ cli_arguments.select { |file| !File.file?(file) }.join('", "') + '"')
+ end
end
- # Initialize and configure the logger.
- # === Loggers and Formatters
- # In Chef 10.x and previous, the Logger was the primary/only way that Chef
- # communicated information to the user. In Chef 10.14, a new system, "output
- # formatters" was added, and in Chef 11.0+ it is the default when running
- # chef in a console (detected by `STDOUT.tty?`). Because output formatters
- # are more complex than the logger system and users have less experience with
- # them, the config option `force_logger` is provided to restore the Chef 10.x
- # behavior.
- #
- # Conversely, for users who want formatter output even when chef is running
- # unattended, the `force_formatter` option is provided.
- #
- # === Auto Log Level
- # When `log_level` is set to `:auto` (default), the log level will be `:warn`
- # when the primary output mode is an output formatter (see
- # +using_output_formatter?+) and `:info` otherwise.
- #
- # === Automatic STDOUT Logging
- # When `force_logger` is configured (e.g., Chef 10 mode), a second logger
- # with output on STDOUT is added when running in a console (STDOUT is a tty)
- # and the configured log_location isn't STDOUT. This accounts for the case
- # that a user has configured a log_location in client.rb, but is running
- # chef-client by hand to troubleshoot a problem.
def configure_logging
configure_log_location
- Chef::Log.init(MonoLogger.new(Chef::Config[:log_location]))
- if want_additional_logger?
- configure_stdout_logger
+ logger.init(MonoLogger.new(chef_config[:log_location][0]))
+ chef_config[:log_location][1..].each do |log_location|
+ logger.loggers << MonoLogger.new(log_location)
end
- Chef::Log.level = resolve_log_level
+ logger.level = resolve_log_level
rescue StandardError => error
- Chef::Log.fatal("Failed to open or create log file at #{Chef::Config[:log_location]}: #{error.class} (#{error.message})")
+ logger.fatal("Failed to open or create log file at #{chef_config[:log_location]}: #{error.class} (#{error.message})")
Chef::Application.fatal!("Aborting due to invalid 'log_location' configuration", error)
end
- # Turn `log_location :syslog` and `log_location :win_evt` into the
- # appropriate loggers.
+ # merge Chef::Config[:log_location] and config[:log_location_cli]
+ # - the nil default value of log_location_cli means STDOUT
+ # - the nil default value of log_location is removed
+ # - Arrays are supported
+ # - syslog + winevt are converted to those specific logger objects
+ #
def configure_log_location
- log_location = Chef::Config[:log_location]
- return unless log_location.respond_to?(:to_sym)
-
- Chef::Config[:log_location] =
- case log_location.to_sym
- when :syslog then Chef::Log::Syslog.new
- when :win_evt then Chef::Log::WinEvt.new
- else log_location # Probably a path; let MonoLogger sort it out
+ log_location_cli = [ config[:log_location_cli] ].flatten.map { |log_location| log_location.nil? ? STDOUT : log_location }
+
+ chef_config[:log_location] = [ chef_config[:log_location], log_location_cli ].flatten.compact.uniq
+
+ chef_config[:log_location].map! do |log_location|
+ case log_location
+ when :syslog, "syslog"
+ force_force_logger
+ logger::Syslog.new
+ when :win_evt, "win_evt"
+ force_force_logger
+ logger::WinEvt.new
+ else
+ # should be a path or STDOUT
+ log_location
end
+ end
end
- def configure_stdout_logger
- stdout_logger = MonoLogger.new(STDOUT)
- stdout_logger.formatter = Chef::Log.logger.formatter
- Chef::Log.loggers << stdout_logger
- end
-
- # Based on config and whether or not STDOUT is a tty, should we setup a
- # secondary logger for stdout?
- def want_additional_logger?
- ( Chef::Config[:log_location] != STDOUT ) && STDOUT.tty? && (!Chef::Config[:daemonize]) && (Chef::Config[:force_logger])
+ # Force the logger by default for the :winevt and :syslog loggers. Since we do not and cannot
+ # support multiple log levels in a mix-and-match situation with formatters and loggers, and the
+ # formatters do not support syslog, we force the formatter off by default and the log level is
+ # thus info by default. Users can add `--force-formatter -l info` to get back formatter output
+ # on STDOUT along with syslog logging.
+ #
+ def force_force_logger
+ chef_config[:force_logger] = true unless chef_config[:force_formatter]
end
- # Use of output formatters is assumed if `force_formatter` is set or if
- # `force_logger` is not set and STDOUT is to a console (tty)
+ # Use of output formatters is assumed if `force_formatter` is set or if `force_logger` is not set
def using_output_formatter?
- Chef::Config[:force_formatter] || (!Chef::Config[:force_logger] && STDOUT.tty?)
+ chef_config[:force_formatter] || !chef_config[:force_logger]
end
- def auto_log_level?
- Chef::Config[:log_level] == :auto
- end
-
- # if log_level is `:auto`, convert it to :warn (when using output formatter)
- # or :info (no output formatter). See also +using_output_formatter?+
+ # The :auto formatter defaults to :warn with the formatter and :info with the logger
def resolve_log_level
- if auto_log_level?
- if using_output_formatter?
- :warn
- else
- :info
- end
+ if chef_config[:log_level] == :auto
+ using_output_formatter? ? :warn : :info
else
- Chef::Config[:log_level]
+ chef_config[:log_level]
end
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]
+ Encoding.default_external = chef_config[:ruby_encoding]
end
# Called prior to starting the application, by the run method
@@ -229,6 +241,15 @@ class Chef
raise Chef::Exceptions::Application, "#{self}: you must override setup_application"
end
+ def check_license_acceptance
+ LicenseAcceptance::Acceptor.check_and_persist!(
+ "infra-client",
+ Chef::VERSION.to_s,
+ logger: logger,
+ provided: Chef::Config[:chef_license]
+ )
+ end
+
# Actually run the application
def run_application
raise Chef::Exceptions::Application, "#{self}: you must override run_application"
@@ -242,12 +263,12 @@ class Chef
Chef::LocalMode.with_server_connectivity do
override_runlist = config[:override_runlist]
- override_runlist ||= [] if specific_recipes.size > 0
@chef_client = Chef::Client.new(
@chef_client_json,
override_runlist: override_runlist,
specific_recipes: specific_recipes,
- runlist: config[:runlist]
+ runlist: config[:runlist],
+ logger: logger
)
@chef_client_json = nil
@@ -269,7 +290,7 @@ class Chef
# win32-process gem exposes some form of :fork for Process
# class. So we are separately ensuring that the platform we're
# running on is not windows before forking.
- Chef::Config[:client_fork] && Process.respond_to?(:fork) && !Chef::Platform.windows?
+ chef_config[:client_fork] && Process.respond_to?(:fork) && !ChefUtils.windows?
end
# Run chef-client once and then exit. If TERM signal is received, ignores the
@@ -277,7 +298,7 @@ class Chef
def run_with_graceful_exit_option
# Override the TERM signal.
trap("TERM") do
- Chef::Log.debug("SIGTERM received during converge," +
+ logger.debug("SIGTERM received during converge," +
" finishing converge to exit normally (send SIGINT to terminate immediately)")
end
@@ -286,52 +307,54 @@ class Chef
end
def fork_chef_client
- Chef::Log.info "Forking chef instance to converge..."
+ logger.info "Forking #{ChefUtils::Dist::Infra::PRODUCT} instance to converge..."
pid = fork do
# Want to allow forked processes to finish converging when
# TERM singal is received (exit gracefully)
trap("TERM") do
- Chef::Log.debug("SIGTERM received during converge," +
+ logger.debug("SIGTERM received during converge," +
" finishing converge to exit normally (send SIGINT to terminate immediately)")
end
- client_solo = Chef::Config[:solo] ? "chef-solo" : "chef-client"
+ client_solo = chef_config[:solo] ? ChefUtils::Dist::Solo::EXEC : ChefUtils::Dist::Infra::CLIENT
$0 = "#{client_solo} worker: ppid=#{Process.ppid};start=#{Time.new.strftime("%R:%S")};"
begin
- Chef::Log.debug "Forked instance now converging"
+ logger.trace "Forked instance now converging"
@chef_client.run
rescue Exception => e
- Chef::Log.error(e.to_s)
+ logger.error(e.to_s)
exit Chef::Application.normalize_exit_code(e)
else
exit 0
end
end
- Chef::Log.debug "Fork successful. Waiting for new chef pid: #{pid}"
+ logger.trace "Fork successful. Waiting for new #{ChefUtils::Dist::Infra::CLIENT} pid: #{pid}"
result = Process.waitpid2(pid)
handle_child_exit(result)
- Chef::Log.debug "Forked instance successfully reaped (pid: #{pid})"
+ logger.trace "Forked instance successfully reaped (pid: #{pid})"
true
end
def handle_child_exit(pid_and_status)
status = pid_and_status[1]
return true if status.success?
+
message = if status.signaled?
- "Chef run process terminated by signal #{status.termsig} (#{Signal.list.invert[status.termsig]})"
+ "#{ChefUtils::Dist::Infra::PRODUCT} run process terminated by signal #{status.termsig} (#{Signal.list.invert[status.termsig]})"
else
- "Chef run process exited unsuccessfully (exit code #{status.exitstatus})"
+ "#{ChefUtils::Dist::Infra::PRODUCT} run process exited unsuccessfully (exit code #{status.exitstatus})"
end
raise Exceptions::ChildConvergeError, message
end
def apply_config(config_content, config_file_path)
- Chef::Config.from_string(config_content, config_file_path)
+ chef_config.from_string(config_content, config_file_path)
rescue Exception => error
- Chef::Log.fatal("Configuration error #{error.class}: #{error.message}")
+ logger.fatal("Configuration error #{error.class}: #{error.message}")
filtered_trace = error.backtrace.grep(/#{Regexp.escape(config_file_path)}/)
- filtered_trace.each { |line| Chef::Log.fatal(" " + line ) }
- Chef::Application.fatal!("Aborting due to error in '#{config_file_path}'", error)
+ filtered_trace.each { |line| logger.fatal(" " + line ) }
+ raise Chef::Exceptions::ConfigurationError.new("Aborting due to error in '#{config_file_path}': #{error}")
+ # Chef::Application.fatal!("Aborting due to error in '#{config_file_path}'", Chef::Exceptions::ConfigurationError.new(error))
end
# This is a hook for testing
@@ -339,18 +362,12 @@ class Chef
ENV
end
- def emit_warnings
- if Chef::Config[:chef_gem_compile_time]
- 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")}"
cause = e.cause if e.respond_to?(:cause)
- while cause != nil
+ until cause.nil?
message << "\n\n>>>> Caused by #{cause.class}: #{cause}\n#{cause.backtrace.join("\n")}"
cause = cause.respond_to?(:cause) ? cause.cause : nil
end
@@ -358,10 +375,14 @@ class Chef
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)
+ Chef::FileCache.store("#{ChefUtils::Dist::Infra::SHORT}-stacktrace.out", chef_stacktrace_out)
+ logger.fatal("Stacktrace dumped to #{Chef::FileCache.load("#{ChefUtils::Dist::Infra::SHORT}-stacktrace.out", false)}")
+ logger.fatal("Please provide the contents of the stacktrace.out file if you file a bug report")
+ if Chef::Config[:always_dump_stacktrace]
+ logger.fatal(message)
+ else
+ logger.debug(message)
+ end
true
end
@@ -371,12 +392,15 @@ class Chef
# Log a fatal error message to both STDERR and the Logger, exit the application
def fatal!(msg, err = nil)
- Chef::Log.fatal(msg)
+ if Chef::Config[:always_dump_stacktrace]
+ msg << "\n#{err.backtrace.join("\n")}"
+ end
+ logger.fatal(msg)
Process.exit(normalize_exit_code(err))
end
def exit!(msg, err = nil)
- Chef::Log.debug(msg)
+ logger.debug(msg)
Process.exit(normalize_exit_code(err))
end
end
diff --git a/lib/chef/application/apply.rb b/lib/chef/application/apply.rb
index 3e3fb58448..3559f8e416 100644
--- a/lib/chef/application/apply.rb
+++ b/lib/chef/application/apply.rb
@@ -17,101 +17,122 @@
# 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_relative "../../chef"
+require_relative "../application"
+require_relative "../client"
+require_relative "../config"
+require_relative "../config_fetcher"
+require_relative "../log"
+require "fileutils" unless defined?(FileUtils)
+require "tempfile" unless defined?(Tempfile)
+require_relative "../providers"
+require_relative "../resources"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+require "license_acceptance/cli_flags/mixlib_cli"
class Chef::Application::Apply < Chef::Application
+ include LicenseAcceptance::CLIFlags::MixlibCLI
- banner "Usage: chef-apply [RECIPE_FILE | -e RECIPE_TEXT | -s] [OPTIONS]"
+ banner "Usage: #{ChefUtils::Dist::Apply::EXEC} [RECIPE_FILE | -e RECIPE_TEXT | -s] [OPTIONS]"
option :execute,
- :short => "-e RECIPE_TEXT",
- :long => "--execute RECIPE_TEXT",
- :description => "Execute resources supplied in a string",
- :proc => nil
+ short: "-e RECIPE_TEXT",
+ long: "--execute RECIPE_TEXT",
+ description: "Execute resources supplied in a string.",
+ proc: nil
option :stdin,
- :short => "-s",
- :long => "--stdin",
- :description => "Execute resources read from STDIN",
- :boolean => true
+ short: "-s",
+ long: "--stdin",
+ description: "Execute resources read from STDIN.",
+ boolean: true
option :json_attribs,
- :short => "-j JSON_ATTRIBS",
- :long => "--json-attributes JSON_ATTRIBS",
- :description => "Load attributes from a JSON file or URL",
- :proc => nil
+ short: "-j JSON_ATTRIBS",
+ long: "--json-attributes JSON_ATTRIBS",
+ 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
+ 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
+ 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) }
+ short: "-F FORMATTER",
+ long: "--format FORMATTER",
+ description: "The output format to use.",
+ proc: lambda { |format| Chef::Config.add_formatter(format) }
option :log_level,
- :short => "-l LEVEL",
- :long => "--log_level LEVEL",
- :description => "Set the log level (debug, info, warn, error, fatal)",
- :proc => lambda { |l| l.to_sym }
+ short: "-l LEVEL",
+ long: "--log_level LEVEL",
+ description: "Set the log level (trace, debug, info, warn, error, fatal).",
+ proc: lambda { |l| l.to_sym }
+
+ option :log_location_cli,
+ short: "-L LOGLOCATION",
+ long: "--logfile LOGLOCATION",
+ description: "Set the log file location, defaults to STDOUT - recommended for daemonizing."
+
+ option :always_dump_stacktrace,
+ long: "--[no-]always-dump-stacktrace",
+ boolean: true,
+ default: false,
+ description: "Always dump the stacktrace regardless of the log_level setting."
option :help,
- :short => "-h",
- :long => "--help",
- :description => "Show this message",
- :on => :tail,
- :boolean => true,
- :show_options => true,
- :exit => 0
+ short: "-h",
+ long: "--help",
+ description: "Show this help message.",
+ on: :tail,
+ boolean: true,
+ show_options: true,
+ exit: 0
option :version,
- :short => "-v",
- :long => "--version",
- :description => "Show chef version",
- :boolean => true,
- :proc => lambda { |v| puts "Chef: #{::Chef::VERSION}" },
- :exit => 0
+ short: "-v",
+ long: "--version",
+ description: "Show #{ChefUtils::Dist::Infra::PRODUCT} version.",
+ boolean: true,
+ proc: lambda { |v| puts "#{ChefUtils::Dist::Infra::PRODUCT}: #{::Chef::VERSION}" },
+ exit: 0
option :why_run,
- :short => "-W",
- :long => "--why-run",
- :description => "Enable whyrun mode",
- :boolean => true
+ short: "-W",
+ long: "--why-run",
+ description: "Enable whyrun mode.",
+ boolean: true
+
+ option :yaml,
+ long: "--yaml",
+ description: "Parse recipe as YAML",
+ 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
+ long: "--[no-]profile-ruby",
+ description: "Dump complete Ruby call graph stack of entire #{ChefUtils::Dist::Infra::PRODUCT} run (expert only).",
+ boolean: true,
+ default: false
option :color,
- :long => "--[no-]color",
- :boolean => true,
- :default => true,
- :description => "Use colored output, defaults to enabled"
+ long: "--[no-]color",
+ boolean: true,
+ default: true,
+ description: "Use colored output, defaults to enabled."
option :minimal_ohai,
- :long => "--minimal-ohai",
- :description => "Only run the bare minimum ohai plugins chef needs to function",
- :boolean => true
+ long: "--minimal-ohai",
+ description: "Only run the bare minimum Ohai plugins #{ChefUtils::Dist::Infra::PRODUCT} needs to function.",
+ boolean: true
attr_reader :json_attribs
@@ -160,7 +181,7 @@ class Chef::Application::Apply < Chef::Application
else
Chef::RunContext.new(@chef_client.node, {}, @chef_client.events)
end
- recipe = Chef::Recipe.new("(chef-apply cookbook)", "(chef-apply recipe)", run_context)
+ recipe = Chef::Recipe.new("(#{ChefUtils::Dist::Apply::EXEC} cookbook)", "(#{ChefUtils::Dist::Apply::EXEC} recipe)", run_context)
[recipe, run_context]
end
@@ -181,7 +202,7 @@ class Chef::Application::Apply < Chef::Application
@recipe_text = STDIN.read
temp_recipe_file
else
- if !ARGV[0]
+ unless ARGV[0]
puts opt_parser
Chef::Application.exit! "No recipe file provided", Chef::Exceptions::RecipeNotFound.new
end
@@ -189,32 +210,38 @@ class Chef::Application::Apply < Chef::Application
@recipe_text, @recipe_fh = read_recipe_file @recipe_filename
end
recipe, run_context = get_recipe_and_run_context
- recipe.instance_eval(@recipe_text, @recipe_filename, 1)
+ if config[:yaml] || File.extname(@recipe_filename) == ".yml"
+ logger.info "Parsing recipe as YAML"
+ recipe.from_yaml(@recipe_text)
+ else
+ recipe.instance_eval(@recipe_text, @recipe_filename, 1)
+ end
runner = Chef::Runner.new(run_context)
- begin
+ catch(:end_client_run_early) do
+
runner.converge
ensure
@recipe_fh.close
+
end
Chef::Platform::Rebooter.reboot_if_needed!(runner)
end
def run_application
- begin
- parse_options
- run_chef_recipe
- Chef::Application.exit! "Exiting", 0
- rescue SystemExit
- raise
- rescue Exception => e
- Chef::Application.debug_stacktrace(e)
- Chef::Application.fatal!("#{e.class}: #{e.message}", e)
- end
+ parse_options
+ run_chef_recipe
+ Chef::Application.exit! "Exiting", 0
+ rescue SystemExit
+ raise
+ rescue Exception => e
+ Chef::Application.debug_stacktrace(e)
+ Chef::Application.fatal!("#{e.class}: #{e.message}", e)
end
- # Get this party started
- def run
+ # Get this party started
+ def run(enforce_license: false)
reconfigure
+ check_license_acceptance if enforce_license
run_application
end
diff --git a/lib/chef/application/base.rb b/lib/chef/application/base.rb
new file mode 100644
index 0000000000..ad8e8b69c2
--- /dev/null
+++ b/lib/chef/application/base.rb
@@ -0,0 +1,455 @@
+#
+# Copyright:: Copyright (c) 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_relative "../application"
+require_relative "../client"
+require_relative "../log"
+require_relative "../config"
+require_relative "../mixin/shell_out"
+require_relative "../config_fetcher"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+require_relative "../daemon"
+require "chef-config/mixin/dot_d"
+require "license_acceptance/cli_flags/mixlib_cli"
+module Mixlib
+ autoload :Archive, "mixlib/archive"
+end
+
+# This is a temporary class being used as a part of an effort to reduce duplication
+# between Chef::Application::Client and Chef::Application::Solo.
+#
+# If you are looking to make edits to the Client/Solo behavior please make changes here.
+#
+# If you are looking to reference or subclass this class, use Chef::Application::Client
+# instead. This base class will be removed once the work is complete and external code
+# will break.
+#
+# @deprecated use Chef::Application::Client instead, this will be removed in Chef-16
+#
+class Chef::Application::Base < Chef::Application
+ include Chef::Mixin::ShellOut
+ include ChefConfig::Mixin::DotD
+ include LicenseAcceptance::CLIFlags::MixlibCLI
+
+ # Mimic self_pipe sleep from Unicorn to capture signals safely
+ SELF_PIPE = [] # rubocop:disable Style/MutableConstant
+
+ option :config_option,
+ long: "--config-option OPTION=VALUE",
+ description: "Override a single configuration option.",
+ proc: lambda { |option, existing|
+ (existing ||= []) << option
+ existing
+ }
+
+ option :once,
+ long: "--once",
+ description: "Cancel any interval or splay options, run #{ChefUtils::Dist::Infra::PRODUCT} once and exit.",
+ boolean: true
+
+ option :formatter,
+ short: "-F FORMATTER",
+ long: "--format FORMATTER",
+ description: "The output format to use.",
+ proc: lambda { |format| Chef::Config.add_formatter(format) }
+
+ option :force_logger,
+ long: "--force-logger",
+ description: "Use logger output instead of formatter output.",
+ boolean: true,
+ default: false
+
+ option :force_formatter,
+ long: "--force-formatter",
+ description: "Use formatter output instead of logger output.",
+ boolean: true,
+ default: false
+
+ option :profile_ruby,
+ long: "--[no-]profile-ruby",
+ description: "Dump complete Ruby call graph stack of entire #{ChefUtils::Dist::Infra::PRODUCT} run (expert only).",
+ boolean: true,
+ default: false
+
+ option :color,
+ long: "--[no-]color",
+ boolean: true,
+ default: true,
+ description: "Use colored output, defaults to enabled."
+
+ option :log_level,
+ short: "-l LEVEL",
+ long: "--log_level LEVEL",
+ description: "Set the log level (auto, trace, debug, info, warn, error, fatal).",
+ proc: lambda { |l| l.to_sym }
+
+ option :log_location_cli,
+ short: "-L LOGLOCATION",
+ long: "--logfile LOGLOCATION",
+ description: "Set the log file location, defaults to STDOUT - recommended for daemonizing."
+
+ option :always_dump_stacktrace,
+ long: "--[no-]always-dump-stacktrace",
+ boolean: true,
+ default: false,
+ description: "Always dump the stacktrace regardless of the log_level setting."
+
+ option :help,
+ short: "-h",
+ long: "--help",
+ description: "Show this help message.",
+ on: :tail,
+ boolean: true,
+ show_options: true,
+ exit: 0
+
+ option :user,
+ short: "-u USER",
+ long: "--user USER",
+ description: "User to set privilege to.",
+ proc: nil
+
+ option :group,
+ short: "-g GROUP",
+ long: "--group GROUP",
+ description: "Group to set privilege to.",
+ proc: nil
+
+ option :lockfile,
+ long: "--lockfile LOCKFILE",
+ description: "Set the lockfile location. Prevents multiple client processes from converging at the same time.",
+ proc: nil
+
+ option :interval,
+ short: "-i SECONDS",
+ long: "--interval SECONDS",
+ description: "Run #{ChefUtils::Dist::Infra::PRODUCT} periodically, in seconds.",
+ proc: lambda { |s| s.to_i }
+
+ option :json_attribs,
+ short: "-j JSON_ATTRIBS",
+ long: "--json-attributes JSON_ATTRIBS",
+ description: "Load attributes from a JSON file or URL.",
+ proc: nil
+
+ option :node_name,
+ short: "-N NODE_NAME",
+ long: "--node-name NODE_NAME",
+ description: "The node name for this client.",
+ proc: nil
+
+ option :splay,
+ short: "-s SECONDS",
+ long: "--splay SECONDS",
+ description: "The splay time for running at intervals, in seconds.",
+ proc: lambda { |s| s.to_i }
+
+ option :environment,
+ short: "-E ENVIRONMENT",
+ long: "--environment ENVIRONMENT",
+ description: "Set the #{ChefUtils::Dist::Infra::PRODUCT} environment on the node."
+
+ option :client_fork,
+ short: "-f",
+ long: "--[no-]fork",
+ description: "Fork #{ChefUtils::Dist::Infra::PRODUCT} process."
+
+ option :why_run,
+ short: "-W",
+ long: "--why-run",
+ description: "Enable whyrun mode.",
+ boolean: true
+
+ option :override_runlist,
+ short: "-o RunlistItem,RunlistItem...",
+ long: "--override-runlist RunlistItem,RunlistItem...",
+ description: "Replace current run list with specified items for a single run.",
+ proc: lambda { |items|
+ items = items.split(",")
+ items.compact.map do |item|
+ Chef::RunList::RunListItem.new(item)
+ end
+ }
+
+ option :run_lock_timeout,
+ long: "--run-lock-timeout SECONDS",
+ description: "Set maximum duration to wait for another client run to finish, default is indefinitely.",
+ proc: lambda { |s| s.to_i }
+
+ option :version,
+ short: "-v",
+ long: "--version",
+ description: "Show #{ChefUtils::Dist::Infra::PRODUCT} version.",
+ boolean: true,
+ proc: lambda { |v| puts "#{ChefUtils::Dist::Infra::PRODUCT}: #{::Chef::VERSION}" },
+ exit: 0
+
+ option :minimal_ohai,
+ long: "--minimal-ohai",
+ description: "Only run the bare minimum Ohai plugins #{ChefUtils::Dist::Infra::PRODUCT} needs to function.",
+ boolean: true
+
+ option :delete_entire_chef_repo,
+ long: "--delete-entire-chef-repo",
+ description: "DANGEROUS: does what it says, only useful with --recipe-url.",
+ boolean: true
+
+ option :ez,
+ long: "--ez",
+ description: "A memorial for Ezra Zygmuntowicz.",
+ boolean: true
+
+ option :target,
+ short: "-t TARGET",
+ long: "--target TARGET",
+ description: "Target #{ChefUtils::Dist::Infra::PRODUCT} against a remote system or device",
+ proc: lambda { |target|
+ Chef::Log.warn "-- EXPERIMENTAL -- Target mode activated, resources and dsl may change without warning -- EXPERIMENTAL --"
+ target
+ }
+
+ option :disable_config,
+ long: "--disable-config",
+ description: "Refuse to load a config file and use defaults. This is for development and not a stable API.",
+ boolean: true
+
+ if Chef::Platform.windows?
+ option :fatal_windows_admin_check,
+ short: "-A",
+ long: "--fatal-windows-admin-check",
+ description: "Fail the run when #{ChefUtils::Dist::Infra::CLIENT} doesn't have administrator privileges on Windows.",
+ boolean: true
+ end
+
+ option :fips,
+ long: "--[no-]fips",
+ description: "Enable FIPS mode.",
+ boolean: true
+
+ option :solo_legacy_mode,
+ long: "--legacy-mode",
+ description: "Run in legacy mode.",
+ boolean: true
+
+ option :chef_server_url,
+ short: "-S CHEFSERVERURL",
+ long: "--server CHEFSERVERURL",
+ description: "The #{ChefUtils::Dist::Server::PRODUCT} URL.",
+ proc: nil
+
+ option :validation_key,
+ short: "-K KEY_FILE",
+ long: "--validation_key KEY_FILE",
+ description: "Set the validation key file location, used for registering new clients.",
+ proc: nil
+
+ option :client_key,
+ short: "-k KEY_FILE",
+ long: "--client_key KEY_FILE",
+ description: "Set the client key file location.",
+ proc: nil
+
+ option :enable_reporting,
+ short: "-R",
+ long: "--enable-reporting",
+ description: "(#{ChefUtils::Dist::Infra::CLIENT} only) reporting data collection for runs.",
+ boolean: true
+
+ option :local_mode,
+ short: "-z",
+ long: "--local-mode",
+ description: "Point at local repository.",
+ boolean: true
+
+ option :chef_zero_host,
+ long: "--chef-zero-host HOST",
+ description: "Host to start #{ChefUtils::Dist::Zero::PRODUCT} on."
+
+ option :chef_zero_port,
+ long: "--chef-zero-port PORT",
+ description: "Port (or port range) to start #{ChefUtils::Dist::Zero::PRODUCT} 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: false
+
+ option :skip_cookbook_sync,
+ long: "--[no-]skip-cookbook-sync",
+ description: "(#{ChefUtils::Dist::Infra::CLIENT} only) Use cached cookbooks without overwriting local differences from the #{ChefUtils::Dist::Server::PRODUCT}.",
+ boolean: false
+
+ 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."
+
+ IMMEDIATE_RUN_SIGNAL = "1".freeze
+ RECONFIGURE_SIGNAL = "H".freeze
+
+ attr_reader :chef_client_json
+
+ def setup_application
+ Chef::Daemon.change_privilege
+ end
+
+ def setup_signal_handlers
+ super
+
+ unless Chef::Platform.windows?
+ SELF_PIPE.replace IO.pipe
+
+ trap("USR1") do
+ Chef::Log.info("SIGUSR1 received, will run now or after the current run")
+ SELF_PIPE[1].putc(IMMEDIATE_RUN_SIGNAL) # wakeup master process from select
+ end
+
+ # Override the trap setup in the parent so we can avoid running reconfigure during a run
+ trap("HUP") do
+ Chef::Log.info("SIGHUP received, will reconfigure now or after the current run")
+ SELF_PIPE[1].putc(RECONFIGURE_SIGNAL) # wakeup master process from select
+ end
+ end
+ end
+
+ # Run the chef client, optionally daemonizing or looping at intervals.
+ def run_application
+ if Chef::Config[:version]
+ puts "#{ChefUtils::Dist::Infra::PRODUCT} version: #{::Chef::VERSION}"
+ end
+
+ if !Chef::Config[:client_fork] || Chef::Config[:once]
+ begin
+ # run immediately without interval sleep, or splay
+ run_chef_client(Chef::Config[:specific_recipes])
+ rescue SystemExit
+ raise
+ rescue Exception => e
+ Chef::Application.fatal!("#{e.class}: #{e.message}", e)
+ end
+ else
+ interval_run_chef_client
+ end
+ end
+
+ private
+
+ def windows_interval_error_message
+ "Windows #{ChefUtils::Dist::Infra::PRODUCT} interval runs are not supported in #{ChefUtils::Dist::Infra::PRODUCT} 15 and later." +
+ "\nConfiguration settings:" +
+ ("\n interval = #{Chef::Config[:interval]} seconds" if Chef::Config[:interval]).to_s +
+ "\nPlease manage #{ChefUtils::Dist::Infra::PRODUCT} as a scheduled task instead."
+ end
+
+ def unforked_interval_error_message
+ "Unforked #{ChefUtils::Dist::Infra::PRODUCT} interval runs are disabled by default." +
+ "\nConfiguration settings:" +
+ ("\n interval = #{Chef::Config[:interval]} seconds" if Chef::Config[:interval]).to_s +
+ "\nEnable #{ChefUtils::Dist::Infra::PRODUCT} interval runs by setting `:client_fork = true` in your config file or adding `--fork` to your command line options."
+ end
+
+ def fetch_recipe_tarball(url, path)
+ require "open-uri" unless defined?(OpenURI)
+ Chef::Log.trace("Download recipes tarball from #{url} to #{path}")
+ if File.exist?(url)
+ FileUtils.cp(url, path)
+ elsif URI::DEFAULT_PARSER.make_regexp.match?(url)
+ File.open(path, "wb") do |f|
+ open(url) do |r|
+ f.write(r.read)
+ end
+ end
+ else
+ Chef::Application.fatal! "You specified --recipe-url but the value is neither a valid URL nor a path to a file that exists on disk." +
+ "Please confirm the location of the tarball and try again."
+ end
+ end
+
+ def interval_run_chef_client
+ if Chef::Config[:daemonize]
+ Chef::Daemon.daemonize(ChefUtils::Dist::Infra::PRODUCT)
+
+ # Start first daemonized run after configured number of seconds
+ if Chef::Config[:daemonize].is_a?(Integer)
+ sleep_then_run_chef_client(Chef::Config[:daemonize])
+ end
+ end
+
+ loop do
+ sleep_then_run_chef_client(time_to_sleep)
+ Chef::Application.exit!("Exiting", 0) unless Chef::Config[:interval]
+ end
+ end
+
+ def sleep_then_run_chef_client(sleep_sec)
+ Chef::Log.trace("Sleeping for #{sleep_sec} seconds")
+
+ # interval_sleep will return early if we received a signal (unless on windows)
+ interval_sleep(sleep_sec)
+
+ run_chef_client(Chef::Config[:specific_recipes])
+
+ reconfigure
+ rescue SystemExit => e
+ raise
+ rescue Exception => e
+ if Chef::Config[:interval]
+ Chef::Log.error("#{e.class}: #{e}")
+ Chef::Log.trace("#{e.class}: #{e}\n#{e.backtrace.join("\n")}")
+ retry
+ end
+
+ Chef::Application.fatal!("#{e.class}: #{e.message}", e)
+ end
+
+ def time_to_sleep
+ duration = 0
+ duration += rand(Chef::Config[:splay]) if Chef::Config[:splay]
+ duration += Chef::Config[:interval] if Chef::Config[:interval]
+ duration
+ end
+
+ # sleep and handle queued signals
+ def interval_sleep(sec)
+ unless SELF_PIPE.empty?
+ # mimic sleep with a timeout on IO.select, listening for signals setup in #setup_signal_handlers
+ return unless IO.select([ SELF_PIPE[0] ], nil, nil, sec)
+
+ signal = SELF_PIPE[0].getc.chr
+
+ return if signal == IMMEDIATE_RUN_SIGNAL # be explicit about this behavior
+
+ # we need to sleep again after reconfigure to avoid stampeding when logrotate runs out of cron
+ if signal == RECONFIGURE_SIGNAL
+ reconfigure
+ interval_sleep(sec)
+ end
+ else
+ sleep(sec)
+ end
+ end
+
+ def for_ezra
+ puts <<~EOH
+ For Ezra Zygmuntowicz:
+ The man who brought you Chef Solo
+ Early contributor to Chef
+ Kind hearted open source advocate
+ Rest in peace, Ezra.
+ EOH
+ end
+
+end
diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb
index cbaa494b71..39ae7adaac 100644
--- a/lib/chef/application/client.rb
+++ b/lib/chef/application/client.rb
@@ -2,7 +2,7 @@
# Author:: AJ Christensen (<aj@chef.io)
# Author:: Christopher Brown (<cb@chef.io>)
# Author:: Mark Mzyk (mmzyk@chef.io)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,295 +17,58 @@
# 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/mixin/shell_out"
-require "chef-config/mixin/dot_d"
-require "mixlib/archive"
-
-class Chef::Application::Client < Chef::Application
- include Chef::Mixin::ShellOut
- include ChefConfig::Mixin::DotD
+require_relative "base"
+require_relative "../handler/error_report"
+require_relative "../workstation_config_loader"
+autoload :URI, "uri"
+require "chef-utils" unless defined?(ChefUtils::CANARY)
+module Mixlib
+ module Authentication
+ autoload :Log, "mixlib/authentication"
+ end
+end
+autoload :Train, "train"
- # Mimic self_pipe sleep from Unicorn to capture signals safely
- SELF_PIPE = []
+# DO NOT MAKE EDITS, see Chef::Application::Base
+#
+# External code may call / subclass or make references to this class.
+#
+class Chef::Application::Client < Chef::Application::Base
option :config_file,
- :short => "-c CONFIG",
- :long => "--config CONFIG",
- :description => "The configuration file to use"
-
- option :config_option,
- :long => "--config-option OPTION=VALUE",
- :description => "Override a single configuration option",
- :proc => lambda { |option, existing|
- (existing ||= []) << option
- existing
- }
-
- option :formatter,
- :short => "-F FORMATTER",
- :long => "--format FORMATTER",
- :description => "output format to use",
- :proc => lambda { |format| Chef::Config.add_formatter(format) }
-
- option :force_logger,
- :long => "--force-logger",
- :description => "Use logger output instead of formatter output",
- :boolean => true,
- :default => false
-
- option :force_formatter,
- :long => "--force-formatter",
- :description => "Use formatter output instead of logger output",
- :boolean => true,
- :default => false
-
- option :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",
- :boolean => true,
- :default => true,
- :description => "Use colored output, defaults to enabled"
-
- option :log_level,
- :short => "-l LEVEL",
- :long => "--log_level LEVEL",
- :description => "Set the log level (auto, debug, info, warn, error, fatal)",
- :proc => lambda { |l| l.to_sym }
-
- option :log_location,
- :short => "-L LOGLOCATION",
- :long => "--logfile LOGLOCATION",
- :description => "Set the log file location, defaults to STDOUT - recommended for daemonizing",
- :proc => nil
+ short: "-c CONFIG",
+ long: "--config CONFIG",
+ description: "The configuration file to use."
- option :help,
- :short => "-h",
- :long => "--help",
- :description => "Show this message",
- :on => :tail,
- :boolean => true,
- :show_options => true,
- :exit => 0
-
- option :user,
- :short => "-u USER",
- :long => "--user USER",
- :description => "User to set privilege to",
- :proc => nil
-
- option :group,
- :short => "-g GROUP",
- :long => "--group GROUP",
- :description => "Group to set privilege to",
- :proc => nil
-
- unless Chef::Platform.windows?
+ unless ChefUtils.windows?
option :daemonize,
- :short => "-d [WAIT]",
- :long => "--daemonize [WAIT]",
- :description =>
- "Daemonize the process. Accepts an optional integer which is the " \
+ short: "-d [WAIT]",
+ long: "--daemonize [WAIT]",
+ description: "Daemonize the process. Accepts an optional integer which is the " \
"number of seconds to wait before the first daemonized run.",
- :proc => lambda { |wait| wait =~ /^\d+$/ ? wait.to_i : true }
+ proc: lambda { |wait| /^\d+$/.match?(wait) ? wait.to_i : true }
end
option :pid_file,
- :short => "-P PID_FILE",
- :long => "--pid PIDFILE",
- :description => "Set the PID file location, for the chef-client daemon process. Defaults to /tmp/chef-client.pid",
- :proc => nil
-
- option :lockfile,
- :long => "--lockfile LOCKFILE",
- :description => "Set the lockfile location. Prevents multiple client processes from converging at the same time",
- :proc => nil
-
- option :interval,
- :short => "-i SECONDS",
- :long => "--interval SECONDS",
- :description => "Run chef-client periodically, in seconds",
- :proc => lambda { |s| s.to_i }
-
- option :once,
- :long => "--once",
- :description => "Cancel any interval or splay options, run chef once and exit",
- :boolean => true
-
- option :json_attribs,
- :short => "-j JSON_ATTRIBS",
- :long => "--json-attributes JSON_ATTRIBS",
- :description => "Load attributes from a JSON file or URL",
- :proc => nil
-
- option :node_name,
- :short => "-N NODE_NAME",
- :long => "--node-name NODE_NAME",
- :description => "The node name for this client",
- :proc => nil
-
- option :splay,
- :short => "-s SECONDS",
- :long => "--splay SECONDS",
- :description => "The splay time for running at intervals, in seconds",
- :proc => lambda { |s| s.to_i }
-
- option :chef_server_url,
- :short => "-S CHEFSERVERURL",
- :long => "--server CHEFSERVERURL",
- :description => "The chef server URL",
- :proc => nil
-
- option :validation_key,
- :short => "-K KEY_FILE",
- :long => "--validation_key KEY_FILE",
- :description => "Set the validation key file location, used for registering new clients",
- :proc => nil
-
- option :client_key,
- :short => "-k KEY_FILE",
- :long => "--client_key KEY_FILE",
- :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"
-
- option :version,
- :short => "-v",
- :long => "--version",
- :description => "Show chef version",
- :boolean => true,
- :proc => lambda { |v| puts "Chef: #{::Chef::VERSION}" },
- :exit => 0
-
- option :override_runlist,
- :short => "-o RunlistItem,RunlistItem...",
- :long => "--override-runlist RunlistItem,RunlistItem...",
- :description => "Replace current run list with specified items for a single run",
- :proc => lambda {|items|
- items = items.split(",")
- items.compact.map do |item|
- Chef::RunList::RunListItem.new(item)
- end
- }
+ short: "-P PID_FILE",
+ long: "--pid PIDFILE",
+ description: "Set the PID file location, for the #{ChefUtils::Dist::Infra::CLIENT} daemon process. Defaults to /tmp/chef-client.pid.",
+ proc: nil
option :runlist,
- :short => "-r RunlistItem,RunlistItem...",
- :long => "--runlist RunlistItem,RunlistItem...",
- :description => "Permanently replace current run list with specified items",
- :proc => lambda {|items|
+ short: "-r RunlistItem,RunlistItem...",
+ long: "--runlist RunlistItem,RunlistItem...",
+ description: "Permanently replace current run list with specified items.",
+ proc: lambda { |items|
items = items.split(",")
items.compact.map do |item|
Chef::RunList::RunListItem.new(item)
end
}
- option :why_run,
- :short => "-W",
- :long => "--why-run",
- :description => "Enable whyrun mode",
- :boolean => true
-
- option :client_fork,
- :short => "-f",
- :long => "--[no-]fork",
- :description => "Fork client",
- :boolean => true
option :recipe_url,
- :long => "--recipe-url=RECIPE_URL",
- :description => "Pull down a remote archive of recipes and unpack it to the cookbook cache. Only used in local mode."
-
- option :enable_reporting,
- :short => "-R",
- :long => "--enable-reporting",
- :description => "Enable reporting data collection for chef runs",
- :boolean => true
-
- option :local_mode,
- :short => "-z",
- :long => "--local-mode",
- :description => "Point chef-client at local repository",
- :boolean => true
-
- option :chef_zero_host,
- :long => "--chef-zero-host HOST",
- :description => "Host to start chef-zero on"
-
- option :chef_zero_port,
- :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 :disable_config,
- :long => "--disable-config",
- :description => "Refuse to load a config file and use defaults. This is for development and not a stable API",
- :boolean => true
-
- option :run_lock_timeout,
- :long => "--run-lock-timeout SECONDS",
- :description => "Set maximum duration to wait for another client run to finish, default is indefinitely.",
- :proc => lambda { |s| s.to_i }
-
- if Chef::Platform.windows?
- option :fatal_windows_admin_check,
- :short => "-A",
- :long => "--fatal-windows-admin-check",
- :description => "Fail the run when chef-client doesn't have administrator privileges on Windows",
- :boolean => true
- end
-
- option :audit_mode,
- :long => "--audit-mode MODE",
- :description => "Enable audit-mode with `enabled`. Disable audit-mode with `disabled`. Skip converge and only perform audits with `audit-only`",
- :proc => lambda { |mo| mo.tr("-", "_").to_sym }
-
- option :minimal_ohai,
- :long => "--minimal-ohai",
- :description => "Only run the bare minimum ohai plugins chef needs to function",
- :boolean => true
-
- option :listen,
- :long => "--[no-]listen",
- :description => "Whether a local mode (-z) server binds to a port",
- :boolean => true
-
- option :fips,
- :long => "--fips",
- :description => "Enable fips mode",
- :boolean => true
-
- option :delete_entire_chef_repo,
- :long => "--delete-entire-chef-repo",
- :description => "DANGEROUS: does what it says, only useful with --recipe-url",
- :boolean => true
-
- option :skip_cookbook_sync,
- :long => "--[no-]skip-cookbook-sync",
- :description => "Use cached cookbooks without overwriting local differences from the server",
- :boolean => false
-
- IMMEDIATE_RUN_SIGNAL = "1".freeze
-
- attr_reader :chef_client_json
+ long: "--recipe-url=RECIPE_URL",
+ description: "Pull down a remote archive of recipes and unpack it to the cookbook cache. Only used in local mode."
# Reconfigure the chef client
# Re-open the JSON attributes and load them into the node
@@ -316,40 +79,52 @@ class Chef::Application::Client < Chef::Application
set_specific_recipes
- Chef::Config[:fips] = config[:fips] if config.has_key? :fips
+ Chef::Config[:fips] = config[:fips] if config.key? :fips
- Chef::Config[:chef_server_url] = config[:chef_server_url] if config.has_key? :chef_server_url
+ Chef::Config[:chef_server_url] = config[:chef_server_url] if config.key? :chef_server_url
- Chef::Config.local_mode = config[:local_mode] if config.has_key?(:local_mode)
+ Chef::Config.local_mode = config[:local_mode] if config.key?(:local_mode)
- if Chef::Config.has_key?(:chef_repo_path) && Chef::Config.chef_repo_path.nil?
+ if Chef::Config.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)
+ if Chef::Config.local_mode && !Chef::Config.key?(:cookbook_path) && !Chef::Config.key?(:chef_repo_path)
Chef::Config.chef_repo_path = Chef::Config.find_chef_repo_path(Dir.pwd)
end
if Chef::Config[:recipe_url]
if !Chef::Config.local_mode
- Chef::Application.fatal!("chef-client recipe-url can be used only in local-mode")
+ Chef::Application.fatal!("recipe-url can be used only in local-mode")
else
if Chef::Config[:delete_entire_chef_repo]
- Chef::Log.debug "Cleanup path #{Chef::Config.chef_repo_path} before extract recipes into it"
- FileUtils.rm_rf(recipes_path, :secure => true)
+ Chef::Log.trace "Cleanup path #{Chef::Config.chef_repo_path} before extract recipes into it"
+ FileUtils.rm_rf(Chef::Config.chef_repo_path, secure: true)
end
- Chef::Log.debug "Creating path #{Chef::Config.chef_repo_path} to extract recipes into"
+ Chef::Log.trace "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")
fetch_recipe_tarball(Chef::Config[:recipe_url], tarball_path)
Mixlib::Archive.new(tarball_path).extract(Chef::Config.chef_repo_path, perms: false, ignore: /^\.$/)
+ config_path = File.join(Chef::Config.chef_repo_path, "#{ChefUtils::Dist::Infra::USER_CONF_DIR}/config.rb")
+ Chef::Config.from_string(IO.read(config_path), config_path) if File.file?(config_path)
end
end
Chef::Config.chef_zero.host = config[:chef_zero_host] if config[:chef_zero_host]
Chef::Config.chef_zero.port = config[:chef_zero_port] if config[:chef_zero_port]
+ if config[:target] || Chef::Config.target
+ Chef::Config.target_mode.host = config[:target] || Chef::Config.target
+ if URI.parse(Chef::Config.target_mode.host).scheme
+ train_config = Train.unpack_target_from_uri(Chef::Config.target_mode.host)
+ Chef::Config.target_mode = train_config
+ end
+ Chef::Config.target_mode.enabled = true
+ Chef::Config.node_name = Chef::Config.target_mode.host unless Chef::Config.node_name
+ end
+
if Chef::Config[:daemonize]
Chef::Config[:interval] ||= 1800
end
@@ -359,29 +134,31 @@ class Chef::Application::Client < Chef::Application
Chef::Config[:splay] = nil
end
- if !Chef::Config[:client_fork] && Chef::Config[:interval] && !Chef::Platform.windows?
- Chef::Application.fatal!(unforked_interval_error_message)
+ # supervisor processes are enabled by default for interval-running processes but not for one-shot runs
+ if Chef::Config[:client_fork].nil?
+ Chef::Config[:client_fork] = !!Chef::Config[:interval]
+ end
+
+ if Chef::Config[:interval]
+ if Chef::Platform.windows?
+ Chef::Application.fatal!(windows_interval_error_message)
+ elsif !Chef::Config[:client_fork]
+ Chef::Application.fatal!(unforked_interval_error_message)
+ end
end
if Chef::Config[:json_attribs]
config_fetcher = Chef::ConfigFetcher.new(Chef::Config[:json_attribs])
@chef_client_json = config_fetcher.fetch_json
end
-
- if mode = config[:audit_mode] || Chef::Config[:audit_mode]
- expected_modes = [:enabled, :disabled, :audit_only]
- unless expected_modes.include?(mode)
- Chef::Application.fatal!(unrecognized_audit_mode(mode))
- end
- end
end
def load_config_file
- if !config.has_key?(:config_file) && !config[:disable_config]
+ if !config.key?(:config_file) && !config[:disable_config]
if config[:local_mode]
config[:config_file] = Chef::WorkstationConfigLoader.new(nil, Chef::Log).config_location
else
- config[:config_file] = Chef::Config.platform_specific_path("/etc/chef/client.rb")
+ config[:config_file] = Chef::Config.platform_specific_path("#{ChefConfig::Config.etc_chef_dir}/client.rb")
end
end
@@ -398,131 +175,4 @@ class Chef::Application::Client < Chef::Application
Ohai::Log.use_log_devices( Chef::Log )
end
- def setup_application
- Chef::Daemon.change_privilege
- end
-
- def setup_signal_handlers
- super
-
- unless Chef::Platform.windows?
- SELF_PIPE.replace IO.pipe
-
- trap("USR1") do
- Chef::Log.info("SIGUSR1 received, waking up")
- SELF_PIPE[1].putc(IMMEDIATE_RUN_SIGNAL) # wakeup master process from select
- end
- end
- end
-
- # Run the chef client, optionally daemonizing or looping at intervals.
- def run_application
- if Chef::Config[:version]
- puts "Chef version: #{::Chef::VERSION}"
- end
-
- if !Chef::Config[:client_fork] || Chef::Config[:once]
- begin
- # run immediately without interval sleep, or splay
- run_chef_client(Chef::Config[:specific_recipes])
- rescue SystemExit
- raise
- rescue Exception => e
- Chef::Application.fatal!("#{e.class}: #{e.message}", e)
- end
- else
- interval_run_chef_client
- end
- end
-
- private
-
- def interval_run_chef_client
- if Chef::Config[:daemonize]
- Chef::Daemon.daemonize("chef-client")
-
- # Start first daemonized run after configured number of seconds
- if Chef::Config[:daemonize].is_a?(Integer)
- sleep_then_run_chef_client(Chef::Config[:daemonize])
- end
- end
-
- loop do
- sleep_then_run_chef_client(time_to_sleep)
- Chef::Application.exit!("Exiting", 0) if !Chef::Config[:interval]
- end
- end
-
- def sleep_then_run_chef_client(sleep_sec)
- @signal = test_signal
- unless @signal == IMMEDIATE_RUN_SIGNAL
- Chef::Log.debug("Sleeping for #{sleep_sec} seconds")
- interval_sleep(sleep_sec)
- end
- @signal = nil
-
- run_chef_client(Chef::Config[:specific_recipes])
- rescue SystemExit => e
- raise
- rescue Exception => e
- if Chef::Config[:interval]
- Chef::Log.error("#{e.class}: #{e}")
- Chef::Log.debug("#{e.class}: #{e}\n#{e.backtrace.join("\n")}")
- retry
- end
-
- Chef::Application.fatal!("#{e.class}: #{e.message}", e)
- end
-
- def test_signal
- @signal = interval_sleep(0)
- end
-
- def time_to_sleep
- duration = 0
- duration += rand(Chef::Config[:splay]) if Chef::Config[:splay]
- duration += Chef::Config[:interval] if Chef::Config[:interval]
- duration
- end
-
- def interval_sleep(sec)
- unless SELF_PIPE.empty?
- client_sleep(sec)
- else
- # Windows
- sleep(sec)
- end
- end
-
- def client_sleep(sec)
- return unless IO.select([ SELF_PIPE[0] ], nil, nil, sec)
- @signal = SELF_PIPE[0].getc.chr
- end
-
- def unforked_interval_error_message
- "Unforked chef-client interval runs are disabled in Chef 12." +
- "\nConfiguration settings:" +
- "#{"\n interval = #{Chef::Config[:interval]} seconds" if Chef::Config[:interval]}" +
- "\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_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_explanation
- end
-
- def fetch_recipe_tarball(url, path)
- Chef::Log.debug("Download recipes tarball from #{url} to #{path}")
- File.open(path, "wb") do |f|
- open(url) do |r|
- f.write(r.read)
- end
- end
- end
end
diff --git a/lib/chef/application/exit_code.rb b/lib/chef/application/exit_code.rb
index 753f1a0d80..26c181fa3d 100644
--- a/lib/chef/application/exit_code.rb
+++ b/lib/chef/application/exit_code.rb
@@ -1,6 +1,6 @@
#
# Author:: Steven Murawski (<smurawski@chef.io>)
-# Copyright:: Copyright 2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,6 +22,7 @@ class Chef
# These are the exit codes defined in Chef RFC 062
# https://github.com/chef/chef-rfc/blob/master/rfc062-exit-status.md
class ExitCode
+ require "chef-utils/dist" unless defined?(ChefUtils::Dist)
# -1 is defined as DEPRECATED_FAILURE in RFC 062, so it is
# not enumerated in an active constant.
@@ -34,73 +35,36 @@ class Chef
REBOOT_SCHEDULED: 35,
REBOOT_NEEDED: 37,
REBOOT_FAILED: 41,
- AUDIT_MODE_FAILURE: 42,
- }
+ # 42 was used by audit mode and should not be reused
+ CONFIG_FAILURE: 43,
+ CLIENT_UPGRADED: 213,
+ }.freeze
DEPRECATED_RFC_062_EXIT_CODES = {
DEPRECATED_FAILURE: -1,
- }
+ }.freeze
class << self
def normalize_exit_code(exit_code = nil)
- if normalization_not_configured?
- normalize_legacy_exit_code_with_warning(exit_code)
- elsif normalization_disabled?
- normalize_legacy_exit_code(exit_code)
+ normalized_exit_code = normalize_legacy_exit_code(exit_code)
+ if valid_exit_codes.include? normalized_exit_code
+ normalized_exit_code
else
- normalize_exit_code_to_rfc(exit_code)
+ Chef::Log.warn(non_standard_exit_code_warning(normalized_exit_code))
+ VALID_RFC_062_EXIT_CODES[:GENERIC_FAILURE]
end
end
- def enforce_rfc_062_exit_codes?
- !normalization_disabled? && !normalization_not_configured?
- end
-
- def notify_reboot_exit_code_deprecation
- return if normalization_disabled?
- notify_on_deprecation(reboot_deprecation_warning)
- end
-
- def notify_deprecated_exit_code
- return if normalization_disabled?
- notify_on_deprecation(deprecation_warning)
- end
-
private
- def normalization_disabled?
- Chef::Config[:exit_status] == :disabled
- end
-
- def normalization_not_configured?
- Chef::Config[:exit_status].nil?
- end
-
- def normalize_legacy_exit_code_with_warning(exit_code)
- normalized_exit_code = normalize_legacy_exit_code(exit_code)
- unless valid_exit_codes.include? normalized_exit_code
- notify_on_deprecation(deprecation_warning)
- end
- normalized_exit_code
- end
-
def normalize_legacy_exit_code(exit_code)
case exit_code
- when Fixnum
+ when Integer
exit_code
when Exception
lookup_exit_code_by_exception(exit_code)
else
- default_exit_code
- end
- end
-
- def normalize_exit_code_to_rfc(exit_code)
- normalized_exit_code = normalize_legacy_exit_code_with_warning(exit_code)
- if valid_exit_codes.include? normalized_exit_code
- normalized_exit_code
- else
VALID_RFC_062_EXIT_CODES[:GENERIC_FAILURE]
end
end
@@ -110,34 +74,21 @@ class Chef
VALID_RFC_062_EXIT_CODES[:SIGINT_RECEIVED]
elsif sigterm_received?(exception)
VALID_RFC_062_EXIT_CODES[:SIGTERM_RECEIVED]
- elsif normalization_disabled? || normalization_not_configured?
- if legacy_exit_code?(exception)
- # We have lots of "Chef::Application.fatal!('', 2)
- # This maintains that behavior at initial introduction
- # and when the RFC exit_status compliance is disabled.
- VALID_RFC_062_EXIT_CODES[:SIGINT_RECEIVED]
- else
- VALID_RFC_062_EXIT_CODES[:GENERIC_FAILURE]
- end
elsif reboot_scheduled?(exception)
VALID_RFC_062_EXIT_CODES[:REBOOT_SCHEDULED]
elsif reboot_needed?(exception)
VALID_RFC_062_EXIT_CODES[:REBOOT_NEEDED]
elsif reboot_failed?(exception)
VALID_RFC_062_EXIT_CODES[:REBOOT_FAILED]
- elsif audit_failure?(exception)
- VALID_RFC_062_EXIT_CODES[:AUDIT_MODE_FAILURE]
+ elsif configuration_failure?(exception)
+ VALID_RFC_062_EXIT_CODES[:CONFIG_FAILURE]
+ elsif client_upgraded?(exception)
+ VALID_RFC_062_EXIT_CODES[:CLIENT_UPGRADED]
else
VALID_RFC_062_EXIT_CODES[:GENERIC_FAILURE]
end
end
- def legacy_exit_code?(exception)
- resolve_exception_array(exception).any? do |e|
- e.is_a? Chef::Exceptions::DeprecatedExitCode
- end
- end
-
def reboot_scheduled?(exception)
resolve_exception_array(exception).any? do |e|
e.is_a? Chef::Exceptions::Reboot
@@ -156,9 +107,15 @@ class Chef
end
end
- def audit_failure?(exception)
+ def configuration_failure?(exception)
+ resolve_exception_array(exception).any? do |e|
+ e.is_a? Chef::Exceptions::ConfigurationError
+ end
+ end
+
+ def client_upgraded?(exception)
resolve_exception_array(exception).any? do |e|
- e.is_a? Chef::Exceptions::AuditError
+ e.is_a? Chef::Exceptions::ClientUpgraded
end
end
@@ -189,34 +146,17 @@ class Chef
end
def notify_on_deprecation(message)
- begin
- Chef.log_deprecation(message)
- rescue Chef::Exceptions::DeprecatedFeatureError
- # Have to rescue this, otherwise this unhandled error preempts
- # the current exit code assignment.
- end
- end
-
- def deprecation_warning
- "Chef RFC 062 (https://github.com/chef/chef-rfc/master/rfc062-exit-status.md) defines the" \
- " exit codes that should be used with Chef. Chef::Application::ExitCode defines valid exit codes" \
- " In a future release, non-standard exit codes will be redefined as" \
- " GENERIC_FAILURE unless `exit_status` is set to `:disabled` in your client.rb."
+ Chef.deprecated(:exit_code, message)
+ rescue Chef::Exceptions::DeprecatedFeatureError
+ # Have to rescue this, otherwise this unhandled error preempts
+ # the current exit code assignment.
end
- def reboot_deprecation_warning
- "Per RFC 062 (https://github.com/chef/chef-rfc/blob/master/rfc062-exit-status.md)" \
- ", when a reboot is requested Chef Client will exit with an exit code of 35, REBOOT_SCHEDULED." \
- " To maintain the current behavior (an exit code of 0), you will need to set `exit_status` to" \
- " `:disabled` in your client.rb"
- end
-
- def default_exit_code
- if normalization_disabled? || normalization_not_configured?
- return DEPRECATED_RFC_062_EXIT_CODES[:DEPRECATED_FAILURE]
- else
- VALID_RFC_062_EXIT_CODES[:GENERIC_FAILURE]
- end
+ def non_standard_exit_code_warning(exit_code)
+ "#{ChefUtils::Dist::Infra::CLIENT} attempted to exit with a non-standard exit code of #{exit_code}." \
+ " The #{ChefUtils::Dist::Infra::PRODUCT} Exit Codes design document (https://github.com/chef/chef-rfc/blob/master/rfc062-exit-status.md)" \
+ " defines the exit codes that should be used with #{ChefUtils::Dist::Infra::CLIENT}. Chef::Application::ExitCode defines" \
+ " valid exit codes Non-standard exit codes are redefined as GENERIC_FAILURE."
end
end
diff --git a/lib/chef/application/knife.rb b/lib/chef/application/knife.rb
index c80d0245f1..7906ce6eaa 100644
--- a/lib/chef/application/knife.rb
+++ b/lib/chef/application/knife.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,141 +15,150 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require "chef/knife"
-require "chef/application"
+require_relative "../knife"
+require_relative "../application"
require "mixlib/log"
require "ohai/config"
-require "chef/monkey_patches/net_http.rb"
+module Net
+ autoload :HTTP, "net/http"
+end
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef::Application::Knife < Chef::Application
- NO_COMMAND_GIVEN = "You need to pass a sub-command (e.g., knife SUB-COMMAND)\n"
+ NO_COMMAND_GIVEN = "You need to pass a sub-command (e.g., knife SUB-COMMAND)\n".freeze
banner "Usage: knife sub-command (options)"
option :config_file,
- :short => "-c CONFIG",
- :long => "--config CONFIG",
- :description => "The configuration file to use",
- :proc => lambda { |path| File.expand_path(path, Dir.pwd) }
+ short: "-c CONFIG",
+ long: "--config CONFIG",
+ description: "The configuration file to use.",
+ proc: lambda { |path| File.expand_path(path, Dir.pwd) }
option :config_option,
- :long => "--config-option OPTION=VALUE",
- :description => "Override a single configuration option",
- :proc => lambda { |option, existing|
+ long: "--config-option OPTION=VALUE",
+ description: "Override a single configuration option.",
+ proc: lambda { |option, existing|
(existing ||= []) << option
existing
}
verbosity_level = 0
option :verbosity,
- :short => "-V",
- :long => "--verbose",
- :description => "More verbose output. Use twice for max verbosity",
- :proc => Proc.new { verbosity_level += 1 },
- :default => 0
+ short: "-V",
+ long: "--verbose",
+ description: "More verbose output. Use twice (-VV) for additional verbosity and three times (-VVV) for maximum verbosity.",
+ proc: Proc.new { verbosity_level += 1 },
+ default: 0
option :color,
- :long => "--[no-]color",
- :boolean => true,
- :default => true,
- :description => "Use colored output, defaults to enabled"
+ long: "--[no-]color",
+ boolean: true,
+ default: true,
+ description: "Use colored output, defaults to enabled."
option :environment,
- :short => "-E ENVIRONMENT",
- :long => "--environment ENVIRONMENT",
- :description => "Set the Chef environment (except for in searches, where this will be flagrantly ignored)"
+ short: "-E ENVIRONMENT",
+ long: "--environment ENVIRONMENT",
+ description: "Set the #{ChefUtils::Dist::Infra::PRODUCT} environment (except for in searches, where this will be flagrantly ignored)."
option :editor,
- :short => "-e EDITOR",
- :long => "--editor EDITOR",
- :description => "Set the editor to use for interactive commands",
- :default => ENV["EDITOR"]
+ short: "-e EDITOR",
+ long: "--editor EDITOR",
+ description: "Set the editor to use for interactive commands.",
+ default: ENV["EDITOR"]
option :disable_editing,
- :short => "-d",
- :long => "--disable-editing",
- :description => "Do not open EDITOR, just accept the data as is",
- :boolean => true,
- :default => false
+ short: "-d",
+ long: "--disable-editing",
+ description: "Do not open EDITOR, just accept the data as is.",
+ boolean: true,
+ default: false
option :help,
- :short => "-h",
- :long => "--help",
- :description => "Show this message",
- :on => :tail,
- :boolean => true
+ short: "-h",
+ long: "--help",
+ description: "Show this help message.",
+ on: :tail,
+ boolean: true
option :node_name,
- :short => "-u USER",
- :long => "--user USER",
- :description => "API Client Username"
+ short: "-u USER",
+ long: "--user USER",
+ description: "#{ChefUtils::Dist::Server::PRODUCT} API client username."
option :client_key,
- :short => "-k KEY",
- :long => "--key KEY",
- :description => "API Client Key",
- :proc => lambda { |path| File.expand_path(path, Dir.pwd) }
+ short: "-k KEY",
+ long: "--key KEY",
+ description: "#{ChefUtils::Dist::Server::PRODUCT} API client key.",
+ proc: lambda { |path| File.expand_path(path, Dir.pwd) }
option :chef_server_url,
- :short => "-s URL",
- :long => "--server-url URL",
- :description => "Chef Server URL"
+ short: "-s URL",
+ long: "--server-url URL",
+ description: "#{ChefUtils::Dist::Server::PRODUCT} URL."
option :yes,
- :short => "-y",
- :long => "--yes",
- :description => "Say yes to all prompts for confirmation"
+ short: "-y",
+ long: "--yes",
+ description: "Say yes to all prompts for confirmation."
option :defaults,
- :long => "--defaults",
- :description => "Accept default values for all questions"
+ long: "--defaults",
+ description: "Accept default values for all questions."
option :print_after,
- :long => "--print-after",
- :description => "Show the data after a destructive operation"
+ long: "--print-after",
+ description: "Show the data after a destructive operation."
option :format,
- :short => "-F FORMAT",
- :long => "--format FORMAT",
- :description => "Which format to use for output",
- :default => "summary"
+ short: "-F FORMAT",
+ long: "--format FORMAT",
+ description: "Which format to use for output.",
+ in: %w{summary text json yaml pp},
+ default: "summary"
option :local_mode,
- :short => "-z",
- :long => "--local-mode",
- :description => "Point knife commands at local repository instead of server",
- :boolean => true
+ short: "-z",
+ long: "--local-mode",
+ description: "Point knife commands at local repository instead of #{ChefUtils::Dist::Server::PRODUCT}.",
+ boolean: true
option :chef_zero_host,
- :long => "--chef-zero-host HOST",
- :description => "Host to start chef-zero on"
+ long: "--chef-zero-host HOST",
+ description: "Host to start #{ChefUtils::Dist::Zero::PRODUCT} on."
option :chef_zero_port,
- :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."
+ long: "--chef-zero-port PORT",
+ description: "Port (or port range) to start #{ChefUtils::Dist::Zero::PRODUCT} 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
+ long: "--[no-]listen",
+ description: "Whether a local mode (-z) server binds to a port.",
+ boolean: false
option :version,
- :short => "-v",
- :long => "--version",
- :description => "Show chef version",
- :boolean => true,
- :proc => lambda { |v| puts "Chef: #{::Chef::VERSION}" },
- :exit => 0
+ short: "-v",
+ long: "--version",
+ description: "Show #{ChefUtils::Dist::Infra::PRODUCT} version.",
+ boolean: true,
+ proc: lambda { |v| puts "#{ChefUtils::Dist::Infra::PRODUCT}: #{::Chef::VERSION}" },
+ exit: 0
option :fips,
- :long => "--[no-]fips",
- :description => "Enable fips mode",
- :boolean => true,
- :default => nil
+ long: "--[no-]fips",
+ description: "Enable FIPS mode.",
+ boolean: true,
+ default: nil
+
+ option :profile,
+ long: "--profile PROFILE",
+ description: "The credentials profile to select."
# Run knife
def run
+ ChefConfig::PathHelper.per_tool_home_environment = "KNIFE_HOME"
Mixlib::Log::Formatter.show_time = false
validate_and_parse_options
quiet_traps
@@ -203,11 +212,20 @@ class Chef::Application::Knife < Chef::Application
Chef::Log.error(fatal_message) if fatal_message
begin
- self.parse_options
+ parse_options
rescue OptionParser::InvalidOption => e
puts "#{e}\n"
end
- puts self.opt_parser
+
+ if want_help?
+ puts "#{ChefUtils::Dist::Infra::PRODUCT}: #{Chef::VERSION}"
+ puts
+ puts "Docs: #{ChefUtils::Dist::Org::KNIFE_DOCS}"
+ puts "Patents: #{ChefUtils::Dist::Org::PATENTS}"
+ puts
+ end
+
+ puts opt_parser
puts
Chef::Knife.list_commands
exit exitcode
diff --git a/lib/chef/application/solo.rb b/lib/chef/application/solo.rb
index 446a0f007d..8264393bb9 100644
--- a/lib/chef/application/solo.rb
+++ b/lib/chef/application/solo.rb
@@ -1,7 +1,7 @@
#
# Author:: AJ Christensen (<aj@chef.io>)
# Author:: Mark Mzyk (mmzyk@chef.io)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,211 +16,45 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require "chef"
-require "chef/application"
-require "chef/application/client"
-require "chef/client"
-require "chef/config"
-require "chef/daemon"
-require "chef/log"
-require "chef/rest"
-require "chef/config_fetcher"
-require "fileutils"
-require "chef/mixin/shell_out"
-require "pathname"
-require "chef-config/mixin/dot_d"
-require "mixlib/archive"
+require_relative "base"
+require_relative "../../chef"
+require_relative "client"
+require "fileutils" unless defined?(FileUtils)
+require "pathname" unless defined?(Pathname)
+require "chef-utils" unless defined?(ChefUtils::CANARY)
-class Chef::Application::Solo < Chef::Application
- include Chef::Mixin::ShellOut
- include ChefConfig::Mixin::DotD
+# DO NOT MAKE EDITS, see Chef::Application::Base
+#
+# Do not reference this class it will be removed in Chef-16
+#
+# @deprecated use Chef::Application::Client instead, this will be removed in Chef-16
+#
+class Chef::Application::Solo < Chef::Application::Base
option :config_file,
- :short => "-c CONFIG",
- :long => "--config CONFIG",
- :default => Chef::Config.platform_specific_path("/etc/chef/solo.rb"),
- :description => "The configuration file to use"
-
- option :config_option,
- :long => "--config-option OPTION=VALUE",
- :description => "Override a single configuration option",
- :proc => lambda { |option, existing|
- (existing ||= []) << option
- existing
- }
-
- option :formatter,
- :short => "-F FORMATTER",
- :long => "--format FORMATTER",
- :description => "output format to use",
- :proc => lambda { |format| Chef::Config.add_formatter(format) }
-
- option :force_logger,
- :long => "--force-logger",
- :description => "Use logger output instead of formatter output",
- :boolean => true,
- :default => false
-
- option :force_formatter,
- :long => "--force-formatter",
- :description => "Use formatter output instead of logger output",
- :boolean => true,
- :default => false
-
- option :profile_ruby,
- :long => "--[no-]profile-ruby",
- :description => "Dump complete Ruby call graph stack of entire Chef run (expert only)",
- :boolean => true,
- :default => false
+ short: "-c CONFIG",
+ long: "--config CONFIG",
+ default: Chef::Config.platform_specific_path("#{ChefConfig::Config.etc_chef_dir}/solo.rb"),
+ description: "The configuration file to use."
- option :color,
- :long => "--[no-]color",
- :boolean => true,
- :default => !Chef::Platform.windows?,
- :description => "Use colored output, defaults to enabled"
-
- option :log_level,
- :short => "-l LEVEL",
- :long => "--log_level LEVEL",
- :description => "Set the log level (debug, info, warn, error, fatal)",
- :proc => lambda { |l| l.to_sym }
-
- option :log_location,
- :short => "-L LOGLOCATION",
- :long => "--logfile LOGLOCATION",
- :description => "Set the log file location, defaults to STDOUT",
- :proc => nil
-
- option :help,
- :short => "-h",
- :long => "--help",
- :description => "Show this message",
- :on => :tail,
- :boolean => true,
- :show_options => true,
- :exit => 0
-
- option :user,
- :short => "-u USER",
- :long => "--user USER",
- :description => "User to set privilege to",
- :proc => nil
-
- option :group,
- :short => "-g GROUP",
- :long => "--group GROUP",
- :description => "Group to set privilege to",
- :proc => nil
-
- unless Chef::Platform.windows?
+ unless ChefUtils.windows?
option :daemonize,
- :short => "-d",
- :long => "--daemonize",
- :description => "Daemonize the process",
- :proc => lambda { |p| true }
+ short: "-d",
+ long: "--daemonize",
+ description: "Daemonize the process.",
+ proc: lambda { |p| true }
end
- option :lockfile,
- :long => "--lockfile LOCKFILE",
- :description => "Set the lockfile location. Prevents multiple processes from converging at the same time",
- :proc => nil
-
- option :interval,
- :short => "-i SECONDS",
- :long => "--interval SECONDS",
- :description => "Run chef-client periodically, in seconds",
- :proc => lambda { |s| s.to_i }
-
- option :json_attribs,
- :short => "-j JSON_ATTRIBS",
- :long => "--json-attributes JSON_ATTRIBS",
- :description => "Load attributes from a JSON file or URL",
- :proc => nil
-
- option :node_name,
- :short => "-N NODE_NAME",
- :long => "--node-name NODE_NAME",
- :description => "The node name for this client",
- :proc => nil
-
- option :splay,
- :short => "-s SECONDS",
- :long => "--splay SECONDS",
- :description => "The splay time for running at intervals, in seconds",
- :proc => lambda { |s| s.to_i }
-
option :recipe_url,
- :short => "-r RECIPE_URL",
- :long => "--recipe-url RECIPE_URL",
- :description => "Pull down a remote gzipped tarball of recipes and untar it to the cookbook cache."
-
- option :version,
- :short => "-v",
- :long => "--version",
- :description => "Show chef version",
- :boolean => true,
- :proc => lambda { |v| puts "Chef: #{::Chef::VERSION}" },
- :exit => 0
-
- option :override_runlist,
- :short => "-o RunlistItem,RunlistItem...",
- :long => "--override-runlist RunlistItem,RunlistItem...",
- :description => "Replace current run list with specified items",
- :proc => lambda {|items|
- items = items.split(",")
- items.compact.map do |item|
- Chef::RunList::RunListItem.new(item)
- end
- }
-
- option :client_fork,
- :short => "-f",
- :long => "--[no-]fork",
- :description => "Fork client",
- :boolean => true
-
- option :why_run,
- :short => "-W",
- :long => "--why-run",
- :description => "Enable whyrun mode",
- :boolean => true
-
- option :ez,
- :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"
-
- option :run_lock_timeout,
- :long => "--run-lock-timeout SECONDS",
- :description => "Set maximum duration to wait for another client run to finish, default is indefinitely.",
- :proc => lambda { |s| s.to_i }
-
- option :minimal_ohai,
- :long => "--minimal-ohai",
- :description => "Only run the bare minimum ohai plugins chef needs to function",
- :boolean => true
-
- option :delete_entire_chef_repo,
- :long => "--delete-entire-chef-repo",
- :description => "DANGEROUS: does what it says, only useful with --recipe-url",
- :boolean => true
-
- option :solo_legacy_mode,
- :long => "--legacy-mode",
- :description => "Run chef-solo in legacy mode",
- :boolean => true
-
- attr_reader :chef_client_json
+ short: "-r RECIPE_URL",
+ long: "--recipe-url RECIPE_URL",
+ description: "Pull down a remote gzipped tarball of recipes and untar it to the cookbook cache."
# Get this party started
- def run
+ def run(enforce_license: false)
setup_signal_handlers
reconfigure
+ check_license_acceptance if enforce_license
for_ezra if Chef::Config[:ez]
if !Chef::Config[:solo_legacy_mode]
Chef::Application::Client.new.run
@@ -237,27 +71,23 @@ class Chef::Application::Solo < Chef::Application
set_specific_recipes
- Chef::Config[:solo] = true
+ Chef::Config[:fips] = config[:fips] if config.key? :fips
- Chef::Log.deprecation("-r MUST be changed to --recipe-url, the -r option will be changed in Chef 13.0") if ARGV.include?("-r")
+ Chef::Config[:solo] = true
if !Chef::Config[:solo_legacy_mode]
# Because we re-parse ARGV when we move to chef-client, we need to tidy up some options first.
ARGV.delete("--ez")
- # -r means something entirely different in chef-client land, so let's replace it with a "safe" value
- if dash_r = ARGV.index("-r")
- ARGV[dash_r] = "--recipe-url"
- end
-
# For back compat reasons, we need to ensure that we try and use the cache_path as a repo first
- Chef::Log.debug "Current chef_repo_path is #{Chef::Config.chef_repo_path}"
+ Chef::Log.trace "Current chef_repo_path is #{Chef::Config.chef_repo_path}"
- if !Chef::Config.has_key?(:cookbook_path) && !Chef::Config.has_key?(:chef_repo_path)
+ if !Chef::Config.key?(:cookbook_path) && !Chef::Config.key?(:chef_repo_path)
Chef::Config.chef_repo_path = Chef::Config.find_chef_repo_path(Chef::Config[:cache_path])
end
Chef::Config[:local_mode] = true
+ Chef::Config[:listen] = false
else
configure_legacy_mode!
end
@@ -268,112 +98,40 @@ class Chef::Application::Solo < Chef::Application
Chef::Config[:interval] ||= 1800
end
- Chef::Application.fatal!(unforked_interval_error_message) if !Chef::Config[:client_fork] && Chef::Config[:interval]
+ # supervisor processes are enabled by default for interval-running processes but not for one-shot runs
+ if Chef::Config[:client_fork].nil?
+ Chef::Config[:client_fork] = !!Chef::Config[:interval]
+ end
+
+ if Chef::Config[:interval]
+ if Chef::Platform.windows?
+ Chef::Application.fatal!(windows_interval_error_message)
+ elsif !Chef::Config[:client_fork]
+ Chef::Application.fatal!(unforked_interval_error_message)
+ end
+ end
if Chef::Config[:recipe_url]
- cookbooks_path = Array(Chef::Config[:cookbook_path]).detect { |e| Pathname.new(e).cleanpath.to_s =~ /\/cookbooks\/*$/ }
+ cookbooks_path = Array(Chef::Config[:cookbook_path]).detect { |e| Pathname.new(e).cleanpath.to_s =~ %r{/cookbooks/*$} }
recipes_path = File.expand_path(File.join(cookbooks_path, ".."))
if Chef::Config[:delete_entire_chef_repo]
- Chef::Log.debug "Cleanup path #{recipes_path} before extract recipes into it"
- FileUtils.rm_rf(recipes_path, :secure => true)
+ Chef::Log.trace "Cleanup path #{recipes_path} before extract recipes into it"
+ FileUtils.rm_rf(recipes_path, secure: true)
end
- Chef::Log.debug "Creating path #{recipes_path} to extract recipes into"
+ Chef::Log.trace "Creating path #{recipes_path} to extract recipes into"
FileUtils.mkdir_p(recipes_path)
tarball_path = File.join(recipes_path, "recipes.tgz")
fetch_recipe_tarball(Chef::Config[:recipe_url], tarball_path)
Mixlib::Archive.new(tarball_path).extract(Chef::Config.chef_repo_path, perms: false, ignore: /^\.$/)
end
- # json_attribs shuld be fetched after recipe_url tarball is unpacked.
+ # json_attribs should be fetched after recipe_url tarball is unpacked.
# Otherwise it may fail if points to local file from tarball.
if Chef::Config[:json_attribs]
config_fetcher = Chef::ConfigFetcher.new(Chef::Config[:json_attribs])
@chef_client_json = config_fetcher.fetch_json
end
-
- # Disable auditing for solo
- Chef::Config[:audit_mode] = :disabled
- end
-
- def setup_application
- Chef::Daemon.change_privilege
- end
-
- def run_application
- if !Chef::Config[:client_fork] || Chef::Config[:once]
- # Run immediately without interval sleep or splay
- begin
- run_chef_client(Chef::Config[:specific_recipes])
- rescue SystemExit
- raise
- rescue Exception => e
- Chef::Application.fatal!("#{e.class}: #{e.message}", e)
- end
- else
- interval_run_chef_client
- end
- end
-
- private
-
- def for_ezra
- puts <<-EOH
-For Ezra Zygmuntowicz:
- The man who brought you Chef Solo
- Early contributor to Chef
- Kind hearted open source advocate
- Rest in peace, Ezra.
-EOH
- end
-
- def interval_run_chef_client
- if Chef::Config[:daemonize]
- Chef::Daemon.daemonize("chef-client")
- end
-
- loop do
- begin
-
- sleep_sec = 0
- sleep_sec += rand(Chef::Config[:splay]) if Chef::Config[:splay]
- sleep_sec += Chef::Config[:interval] if Chef::Config[:interval]
- if sleep_sec != 0
- Chef::Log.debug("Sleeping for #{sleep_sec} seconds")
- sleep(sleep_sec)
- end
-
- run_chef_client
- if !Chef::Config[:interval]
- Chef::Application.exit! "Exiting", 0
- end
- rescue SystemExit => e
- raise
- rescue Exception => e
- if Chef::Config[:interval]
- Chef::Log.error("#{e.class}: #{e}")
- Chef::Log.debug("#{e.class}: #{e}\n#{e.backtrace.join("\n")}")
- retry
- else
- Chef::Application.fatal!("#{e.class}: #{e.message}", e)
- end
- end
- end
- end
-
- def fetch_recipe_tarball(url, path)
- Chef::Log.debug("Download recipes tarball from #{url} to #{path}")
- File.open(path, "wb") do |f|
- open(url) do |r|
- f.write(r.read)
- end
- end
end
- def unforked_interval_error_message
- "Unforked chef-client interval runs are disabled in Chef 12." +
- "\nConfiguration settings:" +
- "#{"\n interval = #{Chef::Config[:interval]} seconds" if Chef::Config[:interval]}" +
- "\nEnable chef-client interval runs by setting `:client_fork = true` in your config file or adding `--fork` to your command line options."
- end
end
diff --git a/lib/chef/application/windows_service.rb b/lib/chef/application/windows_service.rb
index 2f1456ac45..8975556f75 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 2011-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,19 +16,20 @@
# 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/http"
-require "mixlib/cli"
-require "socket"
-require "uri"
+require_relative "../../chef"
+require_relative "../monologger"
+require_relative "../application"
+require_relative "../client"
+require_relative "../config"
+require_relative "../handler/error_report"
+require_relative "../log"
+require_relative "../http"
+require "mixlib/cli" unless defined?(Mixlib::CLI)
+require "socket" unless defined?(Socket)
+require "uri" unless defined?(URI)
require "win32/daemon"
-require "chef/mixin/shell_out"
+require_relative "../mixin/shell_out"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Application
@@ -37,36 +38,36 @@ class Chef
include Chef::Mixin::ShellOut
option :config_file,
- :short => "-c CONFIG",
- :long => "--config CONFIG",
- :default => "#{ENV['SYSTEMDRIVE']}/chef/client.rb",
- :description => ""
+ short: "-c CONFIG",
+ long: "--config CONFIG",
+ default: "#{Chef::Config.etc_chef_dir}/client.rb",
+ description: "The configuration file to use for #{ChefUtils::Dist::Infra::PRODUCT} runs."
option :log_location,
- :short => "-L LOGLOCATION",
- :long => "--logfile LOGLOCATION",
- :description => "Set the log file location"
+ short: "-L LOGLOCATION",
+ long: "--logfile LOGLOCATION",
+ description: "Set the log file location."
option :splay,
- :short => "-s SECONDS",
- :long => "--splay SECONDS",
- :description => "The splay time for running at intervals, in seconds",
- :proc => lambda { |s| s.to_i }
+ short: "-s SECONDS",
+ long: "--splay SECONDS",
+ description: "The splay time for running at intervals, in seconds.",
+ proc: lambda { |s| s.to_i }
option :interval,
- :short => "-i SECONDS",
- :long => "--interval SECONDS",
- :description => "Set the number of seconds to wait between chef-client runs",
- :proc => lambda { |s| s.to_i }
+ short: "-i SECONDS",
+ long: "--interval SECONDS",
+ description: "Set the number of seconds to wait between #{ChefUtils::Dist::Infra::PRODUCT} runs.",
+ proc: lambda { |s| s.to_i }
- DEFAULT_LOG_LOCATION ||= "#{ENV['SYSTEMDRIVE']}/chef/client.log"
+ DEFAULT_LOG_LOCATION ||= "#{Chef::Config.c_chef_dir}/client.log".freeze
def service_init
@service_action_mutex = Mutex.new
@service_signal = ConditionVariable.new
reconfigure
- Chef::Log.info("Chef Client Service initialized")
+ Chef::Log.info("#{ChefUtils::Dist::Infra::CLIENT} Service initialized")
end
def service_main(*startup_parameters)
@@ -77,41 +78,41 @@ class Chef
while running?
# Grab the service_action_mutex to make a chef-client run
@service_action_mutex.synchronize do
- begin
- Chef::Log.info("Next chef-client run will happen in #{timeout} seconds")
- @service_signal.wait(@service_action_mutex, timeout)
-
- # Continue only if service is RUNNING
- next if state != RUNNING
-
- # Reconfigure each time through to pick up any changes in the client file
- Chef::Log.info("Reconfiguring with startup parameters")
- reconfigure(startup_parameters)
- timeout = Chef::Config[:interval]
-
- # Honor splay sleep config
- timeout += rand Chef::Config[:splay]
-
- # run chef-client only if service is in RUNNING state
- next if state != RUNNING
-
- Chef::Log.info("Chef-Client service is starting a chef-client run...")
- run_chef_client
- rescue SystemExit => e
- # Do not raise any of the errors here in order to
- # prevent service crash
- Chef::Log.error("#{e.class}: #{e}")
- rescue Exception => e
- Chef::Log.error("#{e.class}: #{e}")
- end
+
+ Chef::Log.info("Next #{ChefUtils::Dist::Infra::CLIENT} run will happen in #{timeout} seconds")
+ @service_signal.wait(@service_action_mutex, timeout)
+
+ # Continue only if service is RUNNING
+ next if state != RUNNING
+
+ # Reconfigure each time through to pick up any changes in the client file
+ Chef::Log.info("Reconfiguring with startup parameters")
+ reconfigure(startup_parameters)
+ timeout = Chef::Config[:interval]
+
+ # Honor splay sleep config
+ timeout += rand Chef::Config[:splay]
+
+ # run chef-client only if service is in RUNNING state
+ next if state != RUNNING
+
+ Chef::Log.info("#{ChefUtils::Dist::Infra::CLIENT} service is starting a #{ChefUtils::Dist::Infra::CLIENT} run...")
+ run_chef_client
+ rescue SystemExit => e
+ # Do not raise any of the errors here in order to
+ # prevent service crash
+ Chef::Log.error("#{e.class}: #{e}")
+ rescue Exception => e
+ Chef::Log.error("#{e.class}: #{e}")
+
end
end
# Daemon class needs to have all the signal callbacks return
# before service_main returns.
- Chef::Log.debug("Giving signal callbacks some time to exit...")
+ Chef::Log.trace("Giving signal callbacks some time to exit...")
sleep 1
- Chef::Log.debug("Exiting service...")
+ Chef::Log.trace("Exiting service...")
end
################################################################################
@@ -130,12 +131,12 @@ class Chef
break
else
unless run_warning_displayed
- Chef::Log.info("Currently a chef run is happening on this system.")
- Chef::Log.info("Service will stop when run is completed.")
+ Chef::Log.info("Currently a #{ChefUtils::Dist::Infra::PRODUCT} run is happening on this system.")
+ Chef::Log.info("Service will stop when run is completed.")
run_warning_displayed = true
end
- Chef::Log.debug("Waiting for chef-client run...")
+ Chef::Log.trace("Waiting for #{ChefUtils::Dist::Infra::PRODUCT} run...")
sleep 1
end
end
@@ -149,7 +150,7 @@ class Chef
# since this is a PAUSE signal.
if @service_action_mutex.locked?
- Chef::Log.info("Currently a chef-client run is happening.")
+ Chef::Log.info("Currently a #{ChefUtils::Dist::Infra::PRODUCT} run is happening.")
Chef::Log.info("Service will pause once it's completed.")
else
Chef::Log.info("Service is pausing....")
@@ -183,39 +184,38 @@ class Chef
# The chef client will be started in a new process. We have used shell_out to start the chef-client.
# The log_location and config_file of the parent process is passed to the new chef-client process.
# We need to add the --no-fork, as by default it is set to fork=true.
- begin
- Chef::Log.info "Starting chef-client in a new process"
- # Pass config params to the new process
- config_params = " --no-fork"
- config_params += " -c #{Chef::Config[:config_file]}" unless Chef::Config[:config_file].nil?
- # 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.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
- Chef::Log.error "chef-client timed out\n(#{e})"
- Chef::Log.error(<<-EOF)
- Your chef-client run timed out. You can increase the time chef-client is given
+
+ Chef::Log.info "Starting #{ChefUtils::Dist::Infra::CLIENT} in a new process"
+ # Pass config params to the new process
+ config_params = " --no-fork"
+ config_params += " -c #{Chef::Config[:config_file]}" unless Chef::Config[:config_file].nil?
+ # 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(
+ "#{ChefUtils::Dist::Infra::CLIENT}.bat #{config_params}",
+ timeout: Chef::Config[:windows_service][:watchdog_timeout],
+ logger: Chef::Log
+ )
+ Chef::Log.trace (result.stdout).to_s
+ Chef::Log.trace (result.stderr).to_s
+ rescue Mixlib::ShellOut::CommandTimeout => e
+ Chef::Log.error "#{ChefUtils::Dist::Infra::CLIENT} timed out\n(#{e})"
+ Chef::Log.error(<<-EOF)
+ Your #{ChefUtils::Dist::Infra::CLIENT} run timed out. You can increase the time #{ChefUtils::Dist::Infra::CLIENT} is given
to complete by configuring windows_service.watchdog_timeout in your client.rb.
- EOF
- rescue Mixlib::ShellOut::ShellCommandFailed => e
- Chef::Log.warn "Not able to start chef-client in new process (#{e})"
- rescue => e
- Chef::Log.error e
- ensure
- # Once process exits, we log the current process' pid
- Chef::Log.info "Child process exited (pid: #{Process.pid})"
- end
+ EOF
+ rescue Mixlib::ShellOut::ShellCommandFailed => e
+ Chef::Log.warn "Not able to start #{ChefUtils::Dist::Infra::CLIENT} in new process (#{e})"
+ rescue => e
+ Chef::Log.error e
+ ensure
+ # Once process exits, we log the current process' pid
+ Chef::Log.info "Child process exited (pid: #{Process.pid})"
end
def apply_config(config_file_path)
@@ -229,7 +229,7 @@ class Chef
configure_chef startup_parameters
configure_logging
- Chef::Config[:chef_server_url] = config[:chef_server_url] if config.has_key? :chef_server_url
+ Chef::Config[:chef_server_url] = config[:chef_server_url] if config.key? :chef_server_url
unless Chef::Config[:exception_handlers].any? { |h| Chef::Handler::ErrorReport === h }
Chef::Config[:exception_handlers] << Chef::Handler::ErrorReport.new
end
@@ -257,13 +257,13 @@ class Chef
# Based on config and whether or not STDOUT is a tty, should we setup a
# secondary logger for stdout?
def want_additional_logger?
- ( Chef::Config[:log_location] != STDOUT ) && STDOUT.tty? && (!Chef::Config[:daemonize]) && (Chef::Config[:force_logger])
+ ( Chef::Config[:log_location] != STDOUT ) && STDOUT.tty? && !Chef::Config[:daemonize]
end
# Use of output formatters is assumed if `force_formatter` is set or if
- # `force_logger` is not set and STDOUT is to a console (tty)
+ # `force_logger` is not set
def using_output_formatter?
- Chef::Config[:force_formatter] || (!Chef::Config[:force_logger] && STDOUT.tty?)
+ Chef::Config[:force_formatter] || !Chef::Config[:force_logger]
end
def auto_log_level?
@@ -307,23 +307,23 @@ class Chef
begin
case config[:config_file]
- when /^(http|https):\/\//
+ when %r{^(http|https)://}
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
rescue Errno::ENOENT
Chef::Log.warn("*****************************************")
- Chef::Log.warn("Did not find config file: #{config[:config_file]}, using command line options.")
+ Chef::Log.warn("Did not find config file: #{config[:config_file]}. Using command line options instead.")
Chef::Log.warn("*****************************************")
Chef::Config.merge!(config)
rescue SocketError
- Chef::Application.fatal!("Error getting config file #{Chef::Config[:config_file]}", Chef::Exceptions::DeprecatedExitCode.new)
+ Chef::Application.fatal!("Error getting config file #{Chef::Config[:config_file]}")
rescue Chef::Exceptions::ConfigurationError => error
- Chef::Application.fatal!("Error processing config file #{Chef::Config[:config_file]} with error #{error.message}", Chef::Exceptions::DeprecatedExitCode.new)
+ Chef::Application.fatal!("Error processing config file #{Chef::Config[:config_file]} with error #{error.message}")
rescue Exception => error
- Chef::Application.fatal!("Unknown error processing config file #{Chef::Config[:config_file]} with error #{error.message}", Chef::Exceptions::DeprecatedExitCode.new)
+ Chef::Application.fatal!("Unknown error processing config file #{Chef::Config[:config_file]} with error #{error.message}")
end
end
diff --git a/lib/chef/application/windows_service_manager.rb b/lib/chef/application/windows_service_manager.rb
index 6f81dccc67..4f0de26411 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@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,11 +16,12 @@
# limitations under the License.
#
-if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+if RUBY_PLATFORM.match?(/mswin|mingw32|windows/)
require "win32/service"
end
-require "chef/config"
-require "mixlib/cli"
+require_relative "../config"
+require "mixlib/cli" unless defined?(Mixlib::CLI)
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Application
@@ -37,38 +38,38 @@ class Chef
include Mixlib::CLI
option :action,
- :short => "-a ACTION",
- :long => "--action ACTION",
- :default => "status",
- :description => "Action to carry out on chef-service (install, uninstall, status, start, stop, pause, or resume)"
+ short: "-a ACTION",
+ long: "--action ACTION",
+ default: "status",
+ description: "Action to carry out on #{ChefUtils::Dist::Infra::SHORT}-service (install, uninstall, status, start, stop, pause, or resume)."
option :config_file,
- :short => "-c CONFIG",
- :long => "--config CONFIG",
- :default => "#{ENV['SYSTEMDRIVE']}/chef/client.rb",
- :description => "The configuration file to use for chef runs"
+ short: "-c CONFIG",
+ long: "--config CONFIG",
+ default: "#{ChefConfig::Config.c_chef_dir}/client.rb",
+ description: "The configuration file to use for #{ChefUtils::Dist::Infra::PRODUCT} runs."
option :log_location,
- :short => "-L LOGLOCATION",
- :long => "--logfile LOGLOCATION",
- :description => "Set the log file location for chef-service"
+ short: "-L LOGLOCATION",
+ long: "--logfile LOGLOCATION",
+ description: "Set the log file location for #{ChefUtils::Dist::Infra::SHORT}-service."
option :help,
- :short => "-h",
- :long => "--help",
- :description => "Show this message",
- :on => :tail,
- :boolean => true,
- :show_options => true,
- :exit => 0
+ short: "-h",
+ long: "--help",
+ description: "Show this help message.",
+ on: :tail,
+ boolean: true,
+ show_options: true,
+ exit: 0
option :version,
- :short => "-v",
- :long => "--version",
- :description => "Show chef version",
- :boolean => true,
- :proc => lambda { |v| puts "Chef: #{::Chef::VERSION}" },
- :exit => 0
+ short: "-v",
+ long: "--version",
+ description: "Show #{ChefUtils::Dist::Infra::PRODUCT} version.",
+ boolean: true,
+ proc: lambda { |v| puts "#{ChefUtils::Dist::Infra::PRODUCT}: #{::Chef::VERSION}" },
+ exit: 0
def initialize(service_options)
# having to call super in initialize is the most annoying
@@ -77,10 +78,10 @@ class Chef
raise ArgumentError, "Service definition is not provided" if service_options.nil?
- required_options = [:service_name, :service_display_name, :service_description, :service_file_path]
+ required_options = %i{service_name service_display_name service_description service_file_path}
required_options.each do |req_option|
- if !service_options.has_key?(req_option)
+ unless service_options.key?(req_option)
raise ArgumentError, "Service definition doesn't contain required option #{req_option}"
end
end
@@ -114,22 +115,24 @@ 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,
+ 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
+ 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?
+ unless @delayed_start.nil?
+ ::Win32::Service.configure(
+ service_name: @service_name,
+ delayed_start: @delayed_start
+ )
+ end
puts "Service '#{@service_name}' has successfully been installed."
end
when "status"
@@ -161,12 +164,12 @@ class Chef
private
# Just some state constants
- STOPPED = "stopped"
- RUNNING = "running"
- PAUSED = "paused"
+ STOPPED = "stopped".freeze
+ RUNNING = "running".freeze
+ PAUSED = "paused".freeze
def service_exists?
- return ::Win32::Service.exists?(@service_name)
+ ::Win32::Service.exists?(@service_name)
end
def take_action(action = nil, desired_state = nil)
diff --git a/lib/chef/applications.rb b/lib/chef/applications.rb
index 97c896e12e..a30b765c77 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_relative "application/client"
+require_relative "application/knife"
+require_relative "application/solo"
+require_relative "application/apply"
diff --git a/lib/chef/whitelist.rb b/lib/chef/attribute_allowlist.rb
index 58d0bd70c6..18665ce835 100644
--- a/lib/chef/whitelist.rb
+++ b/lib/chef/attribute_allowlist.rb
@@ -1,15 +1,15 @@
-require "chef/exceptions"
+require_relative "exceptions"
class Chef
- class Whitelist
+ class AttributeAllowlist
- # filter takes two arguments - the data you want to filter, and a whitelisted array
- # of keys you want included. You can capture a subtree of the data to filter by
+ # filter takes two arguments - the data you want to filter, and an array of
+ # keys you want included. You can capture a subtree of the data to filter by
# providing a "/"-delimited string of keys. If some key includes "/"-characters,
# you must provide an array of keys instead.
#
- # Whitelist.filter(
+ # AttributeAllowlist.filter(
# { "filesystem" => {
# "/dev/disk" => {
# "size" => "10mb"
@@ -27,26 +27,26 @@ class Chef
# },
# ["network/interfaces/eth0", ["filesystem", "/dev/disk"]])
# will capture the eth0 and /dev/disk subtrees.
- def self.filter(data, whitelist = nil)
- return data if whitelist.nil?
+ def self.filter(data, allowlist = nil)
+ return data if allowlist.nil?
new_data = {}
- whitelist.each do |item|
+ allowlist.each do |item|
add_data(data, new_data, item)
end
new_data
end
- # Walk the data has according to the keys provided by the whitelisted item
- # and add the data to the whitelisting result.
+ # Walk the data has according to the keys provided by the allowlisted item
+ # and add the data to the allowlisting result.
def self.add_data(data, new_data, item)
parts = to_array(item)
all_data = data
filtered_data = new_data
parts[0..-2].each do |part|
- unless all_data[part]
- Chef::Log.warn("Could not find whitelist attribute #{item}.")
+ unless all_data.key?(part)
+ Chef::Log.warn("Could not find allowlist attribute #{item}.")
return nil
end
@@ -58,7 +58,7 @@ class Chef
# Note: You can't do all_data[parts[-1]] here because the value
# may be false-y
unless all_data.key?(parts[-1])
- Chef::Log.warn("Could not find whitelist attribute #{item}.")
+ Chef::Log.warn("Could not find allowlist attribute #{item}.")
return nil
end
@@ -73,7 +73,7 @@ class Chef
# assumed to contain exact keys (that is, Array elements will not be split
# by "/").
def self.to_array(item)
- return item if item.kind_of? Array
+ return item if item.is_a? Array
parts = item.split("/")
parts.shift if !parts.empty? && parts[0].empty?
diff --git a/lib/chef/attribute_blocklist.rb b/lib/chef/attribute_blocklist.rb
new file mode 100644
index 0000000000..929d3dfa36
--- /dev/null
+++ b/lib/chef/attribute_blocklist.rb
@@ -0,0 +1,81 @@
+
+require_relative "exceptions"
+
+class Chef
+ class AttributeBlocklist
+
+ # filter takes two arguments - the data you want to filter, and an array
+ # of keys you want discarded. You can capture a subtree of the data to filter by
+ # providing a "/"-delimited string of keys. If some key includes "/"-characters,
+ # you must provide an array of keys instead.
+ #
+ # AttributeBlocklist.filter(
+ # { "filesystem" => {
+ # "/dev/disk" => {
+ # "size" => "10mb"
+ # },
+ # "map - autohome" => {
+ # "size" => "10mb"
+ # }
+ # },
+ # "network" => {
+ # "interfaces" => {
+ # "eth0" => {...},
+ # "eth1" => {...}
+ # }
+ # }
+ # },
+ # ["network/interfaces/eth0", ["filesystem", "/dev/disk"]])
+ # will exclude the eth0 and /dev/disk subtrees.
+ def self.filter(data, blocklist = nil)
+ return data if blocklist.nil?
+
+ blocklist.each do |item|
+ Chef::Log.warn("Removing item #{item}")
+ remove_data(data, item)
+ end
+ data
+ end
+
+ # Walk the data according to the keys provided by the blocklisted item
+ # to get a reference to the item that will be removed.
+ def self.remove_data(data, item)
+ parts = to_array(item)
+
+ item_ref = data
+ parts[0..-2].each do |part|
+ unless item_ref[part]
+ Chef::Log.warn("Could not find blocklist attribute #{item}.")
+ return nil
+ end
+
+ item_ref = item_ref[part]
+ end
+
+ unless item_ref.key?(parts[-1])
+ Chef::Log.warn("Could not find blocklist attribute #{item}.")
+ return nil
+ end
+
+ item_ref.delete(parts[-1])
+ data
+ end
+
+ private_class_method :remove_data
+
+ # Accepts a String or an Array, and returns an Array of String keys that
+ # are used to traverse the data hash. Strings are split on "/", Arrays are
+ # assumed to contain exact keys (that is, Array elements will not be split
+ # by "/").
+ def self.to_array(item)
+ return item if item.is_a? Array
+
+ parts = item.split("/")
+ parts.shift if !parts.empty? && parts[0].empty?
+ parts
+ end
+
+ private_class_method :to_array
+
+ end
+end
diff --git a/lib/chef/audit/audit_event_proxy.rb b/lib/chef/audit/audit_event_proxy.rb
deleted file mode 100644
index c4d67fa8f4..0000000000
--- a/lib/chef/audit/audit_event_proxy.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-#
-# Author:: Tyler Ball (<tball@chef.io>)
-# Copyright:: Copyright 2014-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.
-#
-
-RSpec::Support.require_rspec_core "formatters/base_text_formatter"
-
-class Chef
- class Audit
- class AuditEventProxy < ::RSpec::Core::Formatters::BaseFormatter
- ::RSpec::Core::Formatters.register self, :stop, :example_group_started
-
- # TODO I don't like this, but I don't see another way to pass this in
- # see rspec files configuration.rb#L671 and formatters.rb#L129
- def self.events=(events)
- @@events = events
- end
-
- def events
- @@events
- end
-
- def example_group_started(notification)
- if notification.group.parent_groups.size == 1
- # top level `control_group` block
- desc = notification.group.description
- Chef::Log.debug("Entered `control_group` block named #{desc}")
- events.control_group_started(desc)
- end
- end
-
- def stop(notification)
- Chef::Log.info("Successfully executed all `control_group` blocks and contained examples")
- notification.examples.each do |example|
- control_group_name, control_data = build_control_from(example)
- e = example.exception
- if e
- events.control_example_failure(control_group_name, control_data, e)
- else
- events.control_example_success(control_group_name, control_data)
- end
- end
- end
-
- private
-
- def build_control_from(example)
- described_class = example.metadata[:described_class]
- if described_class
- resource_type = described_class.class.name.split(":")[-1]
- resource_name = described_class.name
- end
-
- # The following code builds up the context - the list of wrapping `describe` or `control` blocks
- describe_groups = []
- group = example.metadata[:example_group]
- # If the innermost block has a resource instead of a string, don't include it in context
- describe_groups.unshift(group[:description]) if described_class.nil?
- group = group[:parent_example_group]
- until group.nil?
- describe_groups.unshift(group[:description])
- group = group[:parent_example_group]
- end
-
- # We know all of our examples each live in a top-level `control_group` block - get this name now
- outermost_group_desc = describe_groups.shift
-
- return outermost_group_desc, {
- :name => example.description,
- :desc => example.full_description,
- :resource_type => resource_type,
- :resource_name => resource_name,
- :context => describe_groups,
- :line_number => example.metadata[:line_number],
- }
- end
-
- end
- end
-end
diff --git a/lib/chef/audit/audit_reporter.rb b/lib/chef/audit/audit_reporter.rb
deleted file mode 100644
index 8546a21bb4..0000000000
--- a/lib/chef/audit/audit_reporter.rb
+++ /dev/null
@@ -1,176 +0,0 @@
-#
-# Author:: Tyler Ball (<tball@chef.io>)
-#
-# Copyright:: Copyright 2014-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/event_dispatch/base"
-require "chef/audit/control_group_data"
-require "time"
-
-class Chef
- class Audit
- class AuditReporter < EventDispatch::Base
-
- 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"
-
- 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
- run_status.run_context
- end
-
- def audit_phase_start(run_status)
- Chef::Log.debug("Audit Reporter starting")
- @audit_data = AuditData.new(run_status.node.name, run_status.run_id)
- @run_status = run_status
- end
-
- 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)
- end
- end
-
- # If the audit phase failed, its because there was some kind of error in the framework
- # 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, 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)
- end
- end
-
- def run_completed(node)
- post_auditing_data
- end
-
- def run_failed(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)
- if ordered_control_groups.has_key?(name)
- raise Chef::Exceptions::AuditControlGroupDuplicate.new(name)
- end
- metadata = run_context.audits[name].metadata
- ordered_control_groups.store(name, ControlGroupData.new(name, metadata))
- end
-
- def control_example_success(control_group_name, example_data)
- control_group = ordered_control_groups[control_group_name]
- control_group.example_success(example_data)
- end
-
- def control_example_failure(control_group_name, example_data, error)
- control_group = ordered_control_groups[control_group_name]
- control_group.example_failure(example_data, error.message)
- end
-
- # If @audit_enabled is nil or true, we want to run audits
- def auditing_enabled?
- Chef::Config[:audit_mode] != :disabled
- end
-
- private
-
- def post_auditing_data
- unless auditing_enabled?
- Chef::Log.debug("Audit Reports are disabled. Skipping sending reports.")
- return
- end
-
- unless run_status
- Chef::Log.debug("Run failed before audit mode was initialized, not sending audit report to server")
- return
- end
-
- audit_data.start_time = iso8601ify(run_status.start_time)
- audit_data.end_time = iso8601ify(run_status.end_time)
-
- audit_history_url = "controls"
- Chef::Log.debug("Sending audit report (run-id: #{audit_data.run_id})")
- run_data = audit_data.to_hash
-
- 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)}"
- begin
- 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
- # audit reporting. Don't alarm failure in this case.
- if e.response.code == "404"
- Chef::Log.debug("Server doesn't support audit reporting. Skipping report.")
- return
- else
- # Save the audit report to local disk
- error_file = "failed-audit-data.json"
- Chef::FileCache.store(error_file, Chef::JSONCompat.to_json_pretty(run_data), 0640)
- if Chef::Config.chef_zero.enabled
- Chef::Log.debug("Saving audit report to #{Chef::FileCache.load(error_file, false)}")
- else
- Chef::Log.error("Failed to post audit report to server. Saving report to #{Chef::FileCache.load(error_file, false)}")
- end
- end
- else
- Chef::Log.error("Failed to post audit report to server (#{e})")
- end
-
- if Chef::Config[:enable_reporting_url_fatals]
- Chef::Log.error("Reporting fatals enabled. Aborting run.")
- raise
- end
- end
- end
-
- def headers(additional_headers = {})
- options = { "X-Ops-Audit-Report-Protocol-Version" => PROTOCOL_VERSION }
- options.merge(additional_headers)
- end
-
- def encode_gzip(data)
- "".tap do |out|
- Zlib::GzipWriter.wrap(StringIO.new(out)) { |gz| gz << data }
- end
- end
-
- 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
deleted file mode 100644
index 4dffbdf3dd..0000000000
--- a/lib/chef/audit/control_group_data.rb
+++ /dev/null
@@ -1,139 +0,0 @@
-#
-# Author:: Tyler Ball (<tball@chef.io>)
-#
-# Copyright:: Copyright 2014-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 "securerandom"
-
-class Chef
- class Audit
- class AuditData
- attr_reader :node_name, :run_id, :control_groups
- attr_accessor :start_time, :end_time
-
- def initialize(node_name, run_id)
- @node_name = node_name
- @run_id = run_id
- @control_groups = []
- end
-
- def add_control_group(control_group)
- control_groups << control_group
- end
-
- def to_hash
- {
- :node_name => node_name,
- :run_id => run_id,
- :start_time => start_time,
- :end_time => end_time,
- :control_groups => control_groups.collect { |c| c.to_hash },
- }
- end
- end
-
- class ControlGroupData
- attr_reader :name, :status, :number_succeeded, :number_failed, :controls, :metadata
-
- def initialize(name, metadata = {})
- @status = "success"
- @controls = []
- @number_succeeded = 0
- @number_failed = 0
- @name = name
- @metadata = metadata
- end
-
- def example_success(control_data)
- @number_succeeded += 1
- control = create_control(control_data)
- control.status = "success"
- controls << control
- control
- end
-
- def example_failure(control_data, details)
- @number_failed += 1
- @status = "failure"
- control = create_control(control_data)
- control.details = details if details
- control.status = "failure"
- controls << control
- control
- end
-
- def to_hash
- # We sort it so the examples appear in the output in the same order
- # they appeared in the recipe
- controls.sort! { |x, y| x.line_number <=> y.line_number }
- h = {
- :name => name,
- :status => status,
- :number_succeeded => number_succeeded,
- :number_failed => number_failed,
- :controls => controls.collect { |c| c.to_hash },
- }
- # If there is a duplicate key, metadata will overwrite it
- add_display_only_data(h).merge(metadata)
- end
-
- private
-
- def create_control(control_data)
- ControlData.new(control_data)
- end
-
- # The id and control sequence number are ephemeral data - they are not needed
- # to be persisted and can be regenerated at will. They are only needed
- # for display purposes.
- def add_display_only_data(group)
- group[:id] = SecureRandom.uuid
- group[:controls].collect!.with_index do |c, i|
- # i is zero-indexed, and we want the display one-indexed
- c[:sequence_number] = i + 1
- c
- end
- group
- end
-
- end
-
- class ControlData
- attr_reader :name, :resource_type, :resource_name, :context, :line_number
- attr_accessor :status, :details
-
- def initialize(control_data = {})
- control_data.each do |k, v|
- self.instance_variable_set("@#{k}", v)
- end
- end
-
- def to_hash
- h = {
- :name => name,
- :status => status,
- :details => details,
- :resource_type => resource_type,
- :resource_name => resource_name,
- }
- h[:context] = context || []
- h
- end
- end
-
- end
-end
diff --git a/lib/chef/audit/rspec_formatter.rb b/lib/chef/audit/rspec_formatter.rb
deleted file mode 100644
index 234202b684..0000000000
--- a/lib/chef/audit/rspec_formatter.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-#
-# Author:: Serdar Sutay (<serdar@chef.io>)
-# Copyright:: Copyright 2014-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 "rspec/core"
-
-class Chef
- class Audit
- class RspecFormatter < RSpec::Core::Formatters::DocumentationFormatter
- RSpec::Core::Formatters.register self, :close
-
- # @api public
- #
- # Invoked at the very end, `close` allows the formatter to clean
- # up resources, e.g. open streams, etc.
- #
- # @param _notification [NullNotification] (Ignored)
- def close(_notification)
- # Normally Rspec closes the streams it's given. We don't want it for Chef.
- end
- end
- end
-end
diff --git a/lib/chef/audit/runner.rb b/lib/chef/audit/runner.rb
deleted file mode 100644
index 837346381c..0000000000
--- a/lib/chef/audit/runner.rb
+++ /dev/null
@@ -1,196 +0,0 @@
-#
-# Author:: Claire McQuin (<claire@chef.io>)
-# Copyright:: Copyright 2014-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/audit/logger"
-
-class Chef
- class Audit
- class Runner
-
- attr_reader :run_context
- private :run_context
-
- def initialize(run_context)
- @run_context = run_context
- end
-
- def run
- setup
- register_control_groups
- do_run
- end
-
- def failed?
- RSpec.world.reporter.failed_examples.size > 0
- end
-
- def num_failed
- RSpec.world.reporter.failed_examples.size
- end
-
- def num_total
- RSpec.world.reporter.examples.size
- end
-
- def exclusion_pattern
- Regexp.new(".+[\\\/]lib[\\\/]chef[\\\/]")
- end
-
- private
-
- # Prepare to run audits:
- # - Require files
- # - Configure RSpec
- # - Configure Specinfra/Serverspec
- def setup
- require_deps
- configure_rspec
- configure_specinfra
- end
-
- # RSpec uses a global configuration object, RSpec.configuration. We found
- # there was interference between the configuration for audit-mode and
- # the configuration for our own spec tests in these cases:
- # 1. Specinfra and Serverspec modify RSpec.configuration when loading.
- # 2. Setting output/error streams.
- # 3. Adding formatters.
- # 4. Defining example group aliases.
- #
- # Moreover, Serverspec loads its DSL methods into the global namespace,
- # which causes conflicts with the Chef namespace for resources and packages.
- #
- # We wait until we're in the audit-phase of the chef-client run to load
- # these files. This helps with the namespacing problems we saw, and
- # 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"
-
- Specinfra::Backend::Cmd.send(:include, Specinfra::Helper::Set)
- end
-
- # Configure RSpec just the way we like it:
- # - Set location of error and output streams
- # - Add custom audit-mode formatters
- # - Explicitly disable :should syntax
- # - Set :color option according to chef config
- # - Disable exposure of global DSL
- def configure_rspec
- set_streams
- add_formatters
- disable_should_syntax
-
- 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
-
- # Set the error and output streams which audit-mode will use to report
- # human-readable audit information.
- #
- # This should always be called before #add_formatters. RSpec won't allow
- # the output stream to be changed for a formatter once the formatter has
- # been added.
- def set_streams
- RSpec.configuration.output_stream = Chef::Audit::Logger
- RSpec.configuration.error_stream = Chef::Audit::Logger
- end
-
- # Add formatters which we use to
- # 1. Output human-readable data to the output stream,
- # 2. Collect JSON data to send back to the analytics server.
- def add_formatters
- RSpec.configuration.add_formatter(Chef::Audit::AuditEventProxy)
- RSpec.configuration.add_formatter(Chef::Audit::RspecFormatter)
- Chef::Audit::AuditEventProxy.events = run_context.events
- end
-
- # Audit-mode uses RSpec 3. :should syntax is deprecated by default in
- # RSpec 3, so we explicitly disable it here.
- #
- # This can be removed once :should is removed from RSpec.
- def disable_should_syntax
- RSpec.configure do |config|
- config.expect_with :rspec do |c|
- c.syntax = :expect
- end
- end
- end
-
- # Set up the backend for Specinfra/Serverspec. :exec is the local system; on Windows, it is :cmd
- def configure_specinfra
- if Chef::Platform.windows?
- Specinfra.configuration.backend = :cmd
- Specinfra.configuration.os = { :family => "windows" }
- else
- Specinfra.configuration.backend = :exec
- end
- end
-
- # Iterates through the control groups registered to this run_context, builds an
- # example group (RSpec::Core::ExampleGroup) object per control group, and
- # registers the group with the RSpec.world.
- #
- # We could just store an array of example groups and not use RSpec.world,
- # but it may be useful later if we decide to apply our own ordering scheme
- # or use example group filters.
- def register_control_groups
- add_example_group_methods
- run_context.audits.each do |name, group|
- ctl_grp = RSpec::Core::ExampleGroup.__control_group__(*group.args, &group.block)
- RSpec.world.record(ctl_grp)
- end
- end
-
- # Add example group method aliases to RSpec.
- #
- # __control_group__: Used internally to create example groups from the control
- # groups saved in the run_context.
- # control: Used within the context of a control group block, like RSpec's
- # describe or context.
- def add_example_group_methods
- RSpec::Core::ExampleGroup.define_example_group_method :__control_group__
- RSpec::Core::ExampleGroup.define_example_group_method :control
- end
-
- # Run the audits!
- def do_run
- # RSpec::Core::Runner wants to be initialized with an
- # RSpec::Core::ConfigurationOptions object, which is used to process
- # command line configuration arguments. We directly fiddle with the
- # internal RSpec configuration object, so we give nil here and let
- # RSpec pick up its own configuration and world.
- runner = RSpec::Core::Runner.new(nil)
- runner.run_specs(RSpec.world.ordered_example_groups)
- end
-
- end
- end
-end
diff --git a/lib/chef/chef_class.rb b/lib/chef/chef_class.rb
index f019448bd8..bcb5eec4e4 100644
--- a/lib/chef/chef_class.rb
+++ b/lib/chef/chef_class.rb
@@ -1,6 +1,6 @@
#
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,10 +26,12 @@
# 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"
+require_relative "platform/provider_priority_map"
+require_relative "platform/resource_priority_map"
+require_relative "platform/provider_handler_map"
+require_relative "platform/resource_handler_map"
+require_relative "deprecated"
+require_relative "event_dispatch/dsl"
class Chef
class << self
@@ -94,8 +96,8 @@ class Chef
#
# @return [Array<Class>] Modified Priority Array of Provider Classes to use for the resource_name on the node
#
- def set_provider_priority_array(resource_name, priority_array, *filter, &block)
- result = provider_priority_map.set_priority_array(resource_name.to_sym, priority_array, *filter, &block)
+ 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
@@ -109,8 +111,8 @@ class Chef
#
# @return [Array<Class>] Modified Priority Array of Resource Classes to use for the resource_name on the node
#
- def set_resource_priority_array(resource_name, priority_array, *filter, &block)
- result = resource_priority_map.set_priority_array(resource_name.to_sym, priority_array, *filter, &block)
+ 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
@@ -197,31 +199,43 @@ class Chef
#
# 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).
+ # @param type [Symbol] The message to send. This should refer to a class
+ # defined in Chef::Deprecated
+ # @param message [String, nil] An explicit message to display, rather than
+ # the generic one associated with the deprecation.
+ # @param location [String, nil] 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).
+ # @return [void]
#
# @example
- # Chef.deprecation("Deprecated!")
+ # Chef.deprecated(:my_deprecation, message: "This is deprecated!")
#
# @api private this will likely be removed in favor of an as-yet unwritten
# `Chef.log`
- def log_deprecation(message, location = nil)
+ def deprecated(type, message, location = nil)
location ||= Chef::Log.caller_location
+ deprecation = Chef::Deprecated.create(type, message, 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)
+ run_context.events.deprecation(deprecation, location)
+ elsif !deprecation.silenced?
+ Chef::Log.deprecation(deprecation.to_s)
end
end
- end
- # @api private Only for test dependency injection; not evenly implemented as yet.
- def self.path_to(path)
- path
+ # Log a generic deprecation warning that doesn't have a specific class in
+ # Chef::Deprecated.
+ #
+ # This should generally not be used, as the user will not be given a link
+ # to get more information on fixing the deprecation warning.
+ #
+ # @see #deprecated
+ def log_deprecation(message, location = nil)
+ location ||= Chef::Log.caller_location
+ Chef.deprecated(:generic, message, location)
+ end
end
reset!
diff --git a/lib/chef/chef_fs.rb b/lib/chef/chef_fs.rb
index 43a9efd5e2..bb3408b781 100644
--- a/lib/chef/chef_fs.rb
+++ b/lib/chef/chef_fs.rb
@@ -1,4 +1,4 @@
-require "chef/platform"
+require_relative "platform"
#
# ChefFS was designed to be a near-1:1 translation between Chef server endpoints
@@ -53,7 +53,7 @@ require "chef/platform"
class Chef
module ChefFS
def self.windows?
- Chef::Platform.windows?
+ ChefUtils.windows?
end
end
end
diff --git a/lib/chef/chef_fs/chef_fs_data_store.rb b/lib/chef/chef_fs/chef_fs_data_store.rb
index 6b3e830f8d..eeeb96e38b 100644
--- a/lib/chef/chef_fs/chef_fs_data_store.rb
+++ b/lib/chef/chef_fs/chef_fs_data_store.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require "chef/cookbook_manifest"
+require_relative "../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/exceptions"
-require "chef/chef_fs/file_system/memory/memory_root"
-require "fileutils"
+require_relative "file_pattern"
+require_relative "file_system"
+require_relative "file_system/exceptions"
+require_relative "file_system/memory/memory_root"
+require "fileutils" unless defined?(FileUtils)
class Chef
module ChefFS
@@ -172,11 +172,11 @@ class Chef
@memory_store.create_dir(path, name, *options)
else
with_parent_dir(path + [name], *options) do |parent, name|
- begin
- parent.create_child(name, nil)
- rescue Chef::ChefFS::FileSystem::AlreadyExistsError => e
- raise ChefZero::DataStore::DataAlreadyExistsError.new(to_zero_path(e.entry), e)
- end
+
+ parent.create_child(name, nil)
+ rescue Chef::ChefFS::FileSystem::AlreadyExistsError => e
+ raise ChefZero::DataStore::DataAlreadyExistsError.new(to_zero_path(e.entry), e)
+
end
end
end
@@ -204,13 +204,13 @@ class Chef
@memory_store.create(path, name, data, *options)
elsif path[0] == "cookbooks" && path.length == 2
- # Do nothing. The entry gets created when the cookbook is created.
+ # 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])
+ if policies.key?(path[3])
raise ChefZero::DataStore::DataAlreadyExistsError.new(path, group)
end
@@ -246,16 +246,16 @@ class Chef
end
else
- if !data.is_a?(String)
+ unless data.is_a?(String)
raise "set only works with strings"
end
with_parent_dir(path + [name], *options) do |parent, name|
- begin
- parent.create_child(name, data)
- rescue Chef::ChefFS::FileSystem::AlreadyExistsError => e
- raise ChefZero::DataStore::DataAlreadyExistsError.new(to_zero_path(e.entry), e)
- end
+
+ parent.create_child(name, data)
+ rescue Chef::ChefFS::FileSystem::AlreadyExistsError => e
+ raise ChefZero::DataStore::DataAlreadyExistsError.new(to_zero_path(e.entry), e)
+
end
end
end
@@ -265,7 +265,7 @@ class Chef
@memory_store.get(path)
elsif path[0] == "file_store" && path[1] == "repo"
- entry = Chef::ChefFS::FileSystem.resolve_path(chef_fs, path[2..-1].join("/"))
+ entry = Chef::ChefFS::FileSystem.resolve_path(chef_fs, path[2..].join("/"))
begin
entry.read
rescue Chef::ChefFS::FileSystem::NotFoundError => e
@@ -279,6 +279,7 @@ class Chef
if !policy_group["policies"] || !policy_group["policies"][path[3]]
raise ChefZero::DataStore::DataNotFoundError.new(path, entry)
end
+
# The policy group looks like:
# {
# "policies": {
@@ -311,7 +312,7 @@ class Chef
cookbook_type = path[0]
result = nil
begin
- result = Chef::CookbookManifest.new(entry.chef_object, policy_mode: cookbook_type == "cookbook_artifacts").to_hash
+ result = Chef::CookbookManifest.new(entry.chef_object, policy_mode: cookbook_type == "cookbook_artifacts").to_h
rescue Chef::ChefFS::FileSystem::NotFoundError => e
raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
end
@@ -319,14 +320,14 @@ class Chef
result.each_pair do |key, value|
if value.is_a?(Array)
value.each do |file|
- if file.is_a?(Hash) && file.has_key?("checksum")
+ if file.is_a?(Hash) && file.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("/")
+ relative += file[:path].split("/")
file["url"] = ChefZero::RestBase.build_uri(request.base_uri, relative)
end
end
@@ -334,7 +335,7 @@ class Chef
end
if cookbook_type == "cookbook_artifacts"
- result["metadata"] = result["metadata"].to_hash
+ result["metadata"] = result["metadata"].to_h
result["metadata"].delete_if do |key, value|
value == [] ||
(value == {} && !%w{dependencies attributes recipes}.include?(key)) ||
@@ -348,11 +349,11 @@ class Chef
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
+
+ entry.read
+ rescue Chef::ChefFS::FileSystem::NotFoundError => e
+ raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
+
end
end
end
@@ -361,7 +362,7 @@ class Chef
if use_memory_store?(path)
@memory_store.set(path, data, *options)
else
- if !data.is_a?(String)
+ unless data.is_a?(String)
raise "set only works with strings: #{path} = #{data.inspect}"
end
@@ -398,9 +399,10 @@ class Chef
# 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])
+ unless group["policies"] && group["policies"].key?(path[3])
raise ChefZero::DataStore::DataNotFoundError.new(path)
end
+
group["policies"].delete(path[3])
group
end
@@ -413,6 +415,7 @@ class Chef
if result.size == members.size
raise ChefZero::DataStore::DataNotFoundError.new(path)
end
+
result
end
@@ -424,20 +427,21 @@ class Chef
if result.size == invitations.size
raise ChefZero::DataStore::DataNotFoundError.new(path)
end
+
result
end
else
with_entry(path) do |entry|
- begin
- if %w{cookbooks cookbook_artifacts}.include?(path[0]) && path.length >= 3
- entry.delete(true)
- else
- entry.delete(false)
- end
- rescue Chef::ChefFS::FileSystem::NotFoundError => e
- raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
+
+ if %w{cookbooks cookbook_artifacts}.include?(path[0]) && path.length >= 3
+ entry.delete(true)
+ else
+ entry.delete(false)
end
+ rescue Chef::ChefFS::FileSystem::NotFoundError => e
+ raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
+
end
end
end
@@ -457,20 +461,21 @@ class Chef
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)
FileSystemCache.instance.delete!(policy.file_path)
found_policy = true
end
- raise ChefZero::DataStore::DataNotFoundError.new(path) if !found_policy
+ raise ChefZero::DataStore::DataNotFoundError.new(path) unless found_policy
end
else
with_entry(path) do |entry|
- begin
- entry.delete(options.include?(:recursive))
- rescue Chef::ChefFS::FileSystem::NotFoundError => e
- raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
- end
+
+ entry.delete(options.include?(:recursive))
+ rescue Chef::ChefFS::FileSystem::NotFoundError => e
+ raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
+
end
end
end
@@ -482,11 +487,11 @@ class Chef
# LIST /policies
elsif path == [ "policies" ]
with_entry([ path[0] ]) do |policies|
- begin
- policies.children.map { |policy| policy.name[0..-6].rpartition("-")[0] }.uniq
- rescue Chef::ChefFS::FileSystem::NotFoundError
- []
- end
+
+ policies.children.map { |policy| policy.name[0..-6].rpartition("-")[0] }.uniq
+ rescue Chef::ChefFS::FileSystem::NotFoundError
+ []
+
end
# LIST /policies/POLICY/revisions
@@ -502,6 +507,7 @@ class Chef
revisions << revision if name == path[1]
end
raise ChefZero::DataStore::DataNotFoundError.new(path) if revisions.empty?
+
revisions
end
@@ -518,32 +524,33 @@ class Chef
elsif %w{cookbooks cookbook_artifacts}.include?(path[0]) && path.length == 1
with_entry(path) do |entry|
- begin
- 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
- entry.children.map { |child| child.name }
- end
- rescue Chef::ChefFS::FileSystem::NotFoundError
- # If the cookbooks dir doesn't exist, we have no cookbooks (not 404)
- []
+
+ 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
+ entry.children.map(&:name)
end
+ rescue Chef::ChefFS::FileSystem::NotFoundError
+ # If the cookbooks dir doesn't exist, we have no cookbooks (not 404)
+ []
+
end
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] }.
- map { |name, version| version }
+ entry.children.map { |child| split_name_version(child.name) }
+ .select { |name, version| name == path[1] }
+ .map { |name, version| version }
end
if result.empty?
raise ChefZero::DataStore::DataNotFoundError.new(path)
end
+
result
else
# list /cookbooks/name = <single version>
@@ -553,16 +560,16 @@ class Chef
else
result = with_entry(path) do |entry|
- begin
- entry.children.map { |c| zero_filename(c) }.sort
- rescue Chef::ChefFS::FileSystem::NotFoundError => e
- # /cookbooks, /data, etc. never return 404
- if path_always_exists?(path)
- []
- else
- raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
- end
+
+ entry.children.map { |c| zero_filename(c) }.sort
+ rescue Chef::ChefFS::FileSystem::NotFoundError => e
+ # /cookbooks, /data, etc. never return 404
+ if path_always_exists?(path)
+ []
+ else
+ raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
end
+
end
# Older versions of chef-zero do not understand policies and cookbook_artifacts,
@@ -581,7 +588,7 @@ class Chef
# /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])
+ group["policies"] && group["policies"].key?(path[3])
else
path_always_exists?(path) || Chef::ChefFS::FileSystem.resolve_path(chef_fs, to_chef_fs_path(path)).exists?
@@ -611,7 +618,7 @@ class Chef
private
def use_memory_store?(path)
- return path[0] == "sandboxes" || path[0] == "file_store" && path[1] == "checksums" || path == %w{environments _default}
+ path[0] == "sandboxes" || path[0] == "file_store" && path[1] == "checksums" || path == %w{environments _default}
end
def write_cookbook(path, data, *options)
@@ -628,7 +635,7 @@ class Chef
cookbook.each_pair do |key, value|
if value.is_a?(Array)
value.each do |file|
- if file.is_a?(Hash) && file.has_key?("checksum")
+ if file.is_a?(Hash) && file.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
@@ -638,7 +645,7 @@ class Chef
# Create the .uploaded-cookbook-version.json
cookbooks = chef_fs.child(cookbook_type)
- if !cookbooks.exists?
+ unless cookbooks.exists?
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
@@ -717,8 +724,8 @@ class Chef
path[-1] = "#{path[-1]}.json"
end
- # /acls/containers|nodes|... do NOT drop into the next elsif, and do
- # not get .json appended
+ # /acls/containers|nodes|... do NOT drop into the next elsif, and do
+ # not get .json appended
# /nodes|clients|.../x.json
elsif path.length == 2
@@ -768,7 +775,7 @@ class Chef
end
elsif path.length == 2 && path[0] != "cookbooks"
- path[1] = path[1][0..-6]
+ path[1] = path[1].gsub(/\.(rb|json)$/, "")
end
path
@@ -779,15 +786,13 @@ class Chef
end
def path_always_exists?(path)
- return path.length == 1 && BASE_DIRNAMES.include?(path[0])
+ path.length == 1 && BASE_DIRNAMES.include?(path[0])
end
def with_entry(path)
- begin
- yield Chef::ChefFS::FileSystem.resolve_path(chef_fs, to_chef_fs_path(path))
- rescue Chef::ChefFS::FileSystem::NotFoundError => e
- raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
- end
+ yield Chef::ChefFS::FileSystem.resolve_path(chef_fs, to_chef_fs_path(path))
+ rescue Chef::ChefFS::FileSystem::NotFoundError => e
+ raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
end
def with_parent_dir(path, *options)
@@ -848,6 +853,7 @@ class Chef
def ensure_dir(entry)
return entry if entry.exists?
+
parent = entry.parent
if parent
ensure_dir(parent)
diff --git a/lib/chef/chef_fs/command_line.rb b/lib/chef/chef_fs/command_line.rb
index 2d887f4780..1e3aa137cd 100644
--- a/lib/chef/chef_fs/command_line.rb
+++ b/lib/chef/chef_fs/command_line.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/chef_fs/file_system"
-require "chef/chef_fs/file_system/exceptions"
-require "chef/util/diff"
+require_relative "file_system"
+require_relative "file_system/exceptions"
+require_relative "../util/diff"
class Chef
module ChefFS
@@ -44,6 +44,7 @@ class Chef
when :directory_to_file
next if diff_filter && diff_filter !~ /T/
+
if output_mode == :name_only
yield "#{new_path}\n"
elsif output_mode == :name_status
@@ -54,6 +55,7 @@ class Chef
when :file_to_directory
next if diff_filter && diff_filter !~ /T/
+
if output_mode == :name_only
yield "#{new_path}\n"
elsif output_mode == :name_status
@@ -71,6 +73,7 @@ class Chef
new_path += File.extname(old_path)
end
next if diff_filter && diff_filter !~ /D/
+
if output_mode == :name_only
yield "#{new_path}\n"
elsif output_mode == :name_status
@@ -86,6 +89,7 @@ class Chef
when :added
next if diff_filter && diff_filter !~ /A/
+
if output_mode == :name_only
yield "#{new_path}\n"
elsif output_mode == :name_status
@@ -101,6 +105,7 @@ class Chef
when :modified
next if diff_filter && diff_filter !~ /M/
+
if output_mode == :name_only
yield "#{new_path}\n"
elsif output_mode == :name_status
@@ -127,7 +132,7 @@ class Chef
end
end
end
- if !found_match
+ unless found_match
ui.error "#{pattern}: No such file or directory on remote or local" if ui
error = true
end
@@ -146,38 +151,38 @@ class Chef
if old_entry.dir?
if new_entry.dir?
if recurse_depth == 0
- return [ [ :common_subdirectories, old_entry, new_entry ] ]
+ [ [ :common_subdirectories, old_entry, new_entry ] ]
else
- return Chef::ChefFS::Parallelizer.parallelize(Chef::ChefFS::FileSystem.child_pairs(old_entry, new_entry)) do |old_child, new_child|
+ Chef::ChefFS::Parallelizer.parallelize(Chef::ChefFS::FileSystem.child_pairs(old_entry, new_entry)) do |old_child, new_child|
Chef::ChefFS::CommandLine.diff_entries(old_child, new_child, recurse_depth ? recurse_depth - 1 : nil, get_content)
end.flatten(1)
end
# If old is a directory and new is a file
elsif new_entry.exists?
- return [ [ :directory_to_file, old_entry, new_entry ] ]
+ [ [ :directory_to_file, old_entry, new_entry ] ]
# If old is a directory and new does not exist
elsif new_entry.parent.can_have_child?(old_entry.name, old_entry.dir?)
- return [ [ :deleted, old_entry, new_entry ] ]
+ [ [ :deleted, old_entry, new_entry ] ]
# If the new entry does not and *cannot* exist, report that.
else
- return [ [ :new_cannot_upload, old_entry, new_entry ] ]
+ [ [ :new_cannot_upload, old_entry, new_entry ] ]
end
# If new is a directory and old is a file
elsif new_entry.dir?
if old_entry.exists?
- return [ [ :file_to_directory, old_entry, new_entry ] ]
+ [ [ :file_to_directory, old_entry, new_entry ] ]
# If new is a directory and old does not exist
elsif old_entry.parent.can_have_child?(new_entry.name, new_entry.dir?)
- return [ [ :added, old_entry, new_entry ] ]
+ [ [ :added, old_entry, new_entry ] ]
# If the new entry does not and *cannot* exist, report that.
else
- return [ [ :old_cannot_upload, old_entry, new_entry ] ]
+ [ [ :old_cannot_upload, old_entry, new_entry ] ]
end
# Neither is a directory, so they are diffable with file diff
@@ -185,9 +190,9 @@ class Chef
are_same, old_value, new_value = Chef::ChefFS::FileSystem.compare(old_entry, new_entry)
if are_same
if old_value == :none
- return [ [ :both_nonexistent, old_entry, new_entry ] ]
+ [ [ :both_nonexistent, old_entry, new_entry ] ]
else
- return [ [ :same, old_entry, new_entry ] ]
+ [ [ :same, old_entry, new_entry ] ]
end
else
if old_value == :none
@@ -229,17 +234,17 @@ class Chef
end
end
- if old_value == :none || (old_value == nil && !old_entry.exists?)
- return [ [ :added, old_entry, new_entry, old_value, new_value ] ]
+ if old_value == :none || (old_value.nil? && !old_entry.exists?)
+ [ [ :added, old_entry, new_entry, old_value, new_value ] ]
elsif new_value == :none
- return [ [ :deleted, old_entry, new_entry, old_value, new_value ] ]
+ [ [ :deleted, old_entry, new_entry, old_value, new_value ] ]
else
- return [ [ :modified, old_entry, new_entry, old_value, new_value ] ]
+ [ [ :modified, old_entry, new_entry, old_value, new_value ] ]
end
end
end
rescue Chef::ChefFS::FileSystem::FileSystemError => e
- return [ [ :error, old_entry, new_entry, nil, nil, e ] ]
+ [ [ :error, old_entry, new_entry, nil, nil, e ] ]
end
class << self
@@ -266,26 +271,24 @@ class Chef
def diff_text(old_path, new_path, old_value, new_value)
# Copy to tempfiles before diffing
# TODO don't copy things that are already in files! Or find an in-memory diff algorithm
- begin
- new_tempfile = Tempfile.new("new")
- new_tempfile.write(new_value)
- new_tempfile.close
-
- begin
- old_tempfile = Tempfile.new("old")
- old_tempfile.write(old_value)
- old_tempfile.close
-
- result = Chef::Util::Diff.new.udiff(old_tempfile.path, new_tempfile.path)
- result = result.gsub(/^--- #{old_tempfile.path}/, "--- #{old_path}")
- result = result.gsub(/^\+\+\+ #{new_tempfile.path}/, "+++ #{new_path}")
- result
- ensure
- old_tempfile.close!
- end
- ensure
- new_tempfile.close!
- end
+
+ new_tempfile = Tempfile.new("new")
+ new_tempfile.write(new_value)
+ new_tempfile.close
+
+ old_tempfile = Tempfile.new("old")
+ old_tempfile.write(old_value)
+ old_tempfile.close
+
+ result = Chef::Util::Diff.new.udiff(old_tempfile.path, new_tempfile.path)
+ result = result.gsub(/^--- #{old_tempfile.path}/, "--- #{old_path}")
+ result = result.gsub(/^\+\+\+ #{new_tempfile.path}/, "+++ #{new_path}")
+ result
+ rescue => e
+ "!!! Unable to diff #{old_path} and #{new_path} due to #{e}"
+ ensure
+ old_tempfile.close!
+ new_tempfile.close!
end
end
end
diff --git a/lib/chef/chef_fs/config.rb b/lib/chef/chef_fs/config.rb
index 63a1363724..6f9183f40b 100644
--- a/lib/chef/chef_fs/config.rb
+++ b/lib/chef/chef_fs/config.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/log"
-require "chef/chef_fs/path_utils"
+require_relative "../log"
+require_relative "path_utils"
class Chef
module ChefFS
@@ -44,7 +44,7 @@ class Chef
"users" => "user",
"policies" => "policy",
"policy_groups" => "policy_group",
- }
+ }.freeze
INFLECTIONS.each { |k, v| k.freeze; v.freeze }
INFLECTIONS.freeze
@@ -66,7 +66,7 @@ class Chef
# upgrade/migration of older Chef Servers, so they should be considered
# frozen in time.
- CHEF_11_OSS_STATIC_OBJECTS = %w{cookbooks cookbook_artifacts data_bags environments roles}.freeze
+ CHEF_11_OSS_STATIC_OBJECTS = %w{cookbooks data_bags environments roles}.freeze
CHEF_11_OSS_DYNAMIC_OBJECTS = %w{clients nodes users}.freeze
RBAC_OBJECT_NAMES = %w{acls containers groups }.freeze
CHEF_12_OBJECTS = %w{ cookbook_artifacts policies policy_groups client_keys }.freeze
@@ -150,7 +150,7 @@ class Chef
hosted_everything or allow repo_mode to default}
end
# Default to getting *everything* from the server.
- if !@chef_config[:repo_mode]
+ unless @chef_config[:repo_mode]
if is_hosted?
@chef_config[:repo_mode] = "hosted_everything"
else
@@ -164,7 +164,7 @@ class Chef
attr_reader :cookbook_version
def is_hosted?
- @chef_config[:chef_server_url] =~ /\/+organizations\/.+/
+ @chef_config[:chef_server_url] =~ %r{/+organizations/.+}
end
def chef_fs
@@ -172,8 +172,8 @@ class Chef
end
def create_chef_fs
- 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)
+ require_relative "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
@@ -181,7 +181,7 @@ class Chef
end
def create_local_fs
- require "chef/chef_fs/file_system/repository/chef_repository_file_system_root_dir"
+ require_relative "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
@@ -272,6 +272,7 @@ class Chef
# cookbooks -> cookbook_path
singular_name = INFLECTIONS[object_name]
raise "Unknown object name #{object_name}" unless singular_name
+
variable_name = "#{singular_name}_path"
paths = Array(@chef_config[variable_name]).flatten
result[object_name] = paths.map { |path| File.expand_path(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 6c8833004a..e64f3d245c 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_relative "data_handler_base"
class Chef
module ChefFS
@@ -13,7 +13,7 @@ class Chef
"delete" => {},
"grant" => {},
})
- result.keys.each do |key|
+ result.each_key do |key|
result[key] = normalize_hash(result[key], { "actors" => [], "groups" => [] })
result[key]["actors"] = result[key]["actors"].sort
result[key]["groups"] = result[key]["groups"].sort
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 5e120035ac..3e5c4f1b84 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_relative "data_handler_base"
+require_relative "../../api_client"
class Chef
module ChefFS
@@ -25,7 +25,7 @@ class Chef
end
def preserve_key?(key)
- return key == "name"
+ key == "name"
end
def chef_class
diff --git a/lib/chef/chef_fs/data_handler/client_key_data_handler.rb b/lib/chef/chef_fs/data_handler/client_key_data_handler.rb
index 6276413bcf..fc81a658cf 100644
--- a/lib/chef/chef_fs/data_handler/client_key_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/client_key_data_handler.rb
@@ -1,5 +1,5 @@
-require "chef/chef_fs/data_handler/data_handler_base"
-require "chef/api_client"
+require_relative "data_handler_base"
+require_relative "../../api_client"
class Chef
module ChefFS
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 04973b5135..25bdf73b04 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_relative "data_handler_base"
class Chef
module ChefFS
@@ -12,7 +12,7 @@ class Chef
end
def preserve_key?(key)
- return key == "containername"
+ key == "containername"
end
# Verify that the JSON hash for this type has a key that matches its name.
@@ -24,7 +24,7 @@ class Chef
def verify_integrity(object, entry)
base_name = remove_dot_json(entry.name)
if object["containername"] != base_name
- yield("Name in #{entry.path_for_printing} must be '#{base_name}' (is '#{object['containername']}')")
+ yield("Name in #{entry.path_for_printing} must be '#{base_name}' (is '#{object["containername"]}')")
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 156c1eef4e..ee1480c62e 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_relative "data_handler_base"
+require_relative "../../cookbook/metadata"
class Chef
module ChefFS
@@ -24,7 +24,7 @@ class Chef
end
def preserve_key?(key)
- return key == "cookbook_name" || key == "version"
+ %w{cookbook_name version}.include?(key)
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 c6b6449d52..4f9a7ae151 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,10 +1,12 @@
-require "chef/chef_fs/data_handler/data_handler_base"
-require "chef/data_bag_item"
+require_relative "data_handler_base"
+require_relative "../../data_bag_item"
class Chef
module ChefFS
module DataHandler
class DataBagItemDataHandler < DataHandlerBase
+ RESERVED_NAMES = /^(node|role|environment|client)$/.freeze
+
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"]
@@ -35,7 +37,7 @@ class Chef
end
def preserve_key?(key)
- return key == "id"
+ key == "id"
end
def chef_class
@@ -43,6 +45,7 @@ class Chef
end
# Verify that the JSON hash for this type has a key that matches its name.
+ # Also check that the data bag name is not a reserved search index name.
#
# @param object [Object] JSON hash of the object
# @param entry [Chef::ChefFS::FileSystem::BaseFSObject] filesystem object we are verifying
@@ -51,7 +54,9 @@ class Chef
def verify_integrity(object, entry)
base_name = remove_dot_json(entry.name)
if object["raw_data"]["id"] != base_name
- yield("ID in #{entry.path_for_printing} must be '#{base_name}' (is '#{object['raw_data']['id']}')")
+ yield("ID in #{entry.path_for_printing} must be '#{base_name}' (is '#{object["raw_data"]["id"]}')")
+ elsif RESERVED_NAMES.match?(entry.parent.name)
+ yield("Data bag name ('#{entry.parent.name}') must not match #{RESERVED_NAMES.inspect}")
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 3668f77dd5..d51e54e8ab 100644
--- a/lib/chef/chef_fs/data_handler/data_handler_base.rb
+++ b/lib/chef/chef_fs/data_handler/data_handler_base.rb
@@ -56,19 +56,22 @@ class Chef
# 2. Put the actual values in the order of the defaults
# 3. Move any other values to the end
#
- # == Example
- #
+ # @example
# normalize_hash({x: 100, c: 2, a: 1}, { a: 10, b: 20, c: 30})
# -> { a: 1, b: 20, c: 2, x: 100}
#
def normalize_hash(object, defaults)
# Make a normalized result in the specified order for diffing
result = {}
- defaults.each_pair do |key, default|
- result[key] = object.has_key?(key) ? object[key] : default
+ defaults.each_pair do |key, value|
+ result[key] = object.is_a?(Hash) && object.key?(key) ? object[key] : value
end
- object.each_pair do |key, value|
- result[key] = value if !result.has_key?(key)
+ if object.is_a?(Hash)
+ object.each_pair do |key, value|
+ result[key] = value unless result.key?(key)
+ end
+ else
+ Chef::Log.warn "Encountered invalid object during normalization. Using these defaults #{defaults}"
end
result
end
@@ -111,7 +114,7 @@ class Chef
def from_ruby(path)
r = chef_class.new
r.from_file(path)
- r.to_hash
+ r.to_h
end
#
@@ -140,8 +143,7 @@ class Chef
# the keys specified in "keys"; anything else must be emitted by the
# caller.
#
- # == Example
- #
+ # @example
# to_ruby_keys({"name" => "foo", "environment" => "desert", "foo": "bar"}, [ "name", "environment" ])
# ->
# 'name "foo"
@@ -195,7 +197,7 @@ class Chef
def verify_integrity(object, entry)
base_name = remove_file_extension(entry.name)
if object["name"] != base_name
- yield("Name must be '#{base_name}' (is '#{object['name']}')")
+ yield("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 68f6daee9a..e3aa242fc9 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_relative "data_handler_base"
+require_relative "../../environment"
class Chef
module ChefFS
@@ -18,7 +18,7 @@ class Chef
end
def preserve_key?(key)
- return key == "name"
+ key == "name"
end
def chef_class
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 7f38784826..3fffec5d11 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_relative "data_handler_base"
+require_relative "../../api_client"
class Chef
module ChefFS
@@ -41,7 +41,7 @@ class Chef
end
def preserve_key?(key)
- return key == "name"
+ 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 36a7bf545b..c4698afd2b 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_relative "data_handler_base"
+require_relative "../../node"
class Chef
module ChefFS
@@ -22,7 +22,7 @@ class Chef
end
def preserve_key?(key)
- return key == "name"
+ 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 0facd5d55d..01d227ffaf 100644
--- a/lib/chef/chef_fs/data_handler/organization_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/organization_data_handler.rb
@@ -1,22 +1,21 @@
-require "chef/chef_fs/data_handler/data_handler_base"
+require_relative "data_handler_base"
class Chef
module ChefFS
module DataHandler
class OrganizationDataHandler < DataHandlerBase
def normalize(organization, entry)
- result = normalize_hash(organization, {
+ normalize_hash(organization, {
"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"
+ key == "name"
end
# Verify that the JSON hash for this type has a key that matches its name.
@@ -27,7 +26,7 @@ class Chef
# @yieldparam [s<string>] error message
def verify_integrity(object, entry)
if entry.org != object["name"]
- yield("Name must be '#{entry.org}' (is '#{object['name']}')")
+ yield("Name must be '#{entry.org}' (is '#{object["name"]}')")
end
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 c5a5f873c5..b3be2a962f 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_relative "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 }.compact.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 8e452a413c..944d3fa0a5 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,4 +1,4 @@
-require "chef/chef_fs/data_handler/data_handler_base"
+require_relative "data_handler_base"
class Chef
module ChefFS
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 fa7bbe9101..8ae749af73 100644
--- a/lib/chef/chef_fs/data_handler/policy_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/policy_data_handler.rb
@@ -1,4 +1,4 @@
-require "chef/chef_fs/data_handler/data_handler_base"
+require_relative "data_handler_base"
class Chef
module ChefFS
@@ -28,18 +28,18 @@ class Chef
# Verify that the JSON hash for this type has a key that matches its name.
#
- # @param object [Object] JSON hash of the object
+ # @param object_data [Object] JSON hash of the object
# @param entry [Chef::ChefFS::FileSystem::BaseFSObject] filesystem object we are verifying
# @yield [s] callback to handle errors
# @yieldparam [s<string>] error message
def verify_integrity(object_data, entry)
name, revision = name_and_revision(entry.name)
if object_data["name"] != name
- yield("Object name '#{object_data['name']}' doesn't match entry '#{name}'.")
+ yield("Object name '#{object_data["name"]}' doesn't match entry '#{name}'.")
end
if object_data["revision_id"] != revision
- yield("Object revision ID '#{object_data['revision_id']}' doesn't match entry '#{revision}'.")
+ yield("Object revision ID '#{object_data["revision_id"]}' doesn't match entry '#{revision}'.")
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
index f7aa92373c..bb0d4efb1c 100644
--- a/lib/chef/chef_fs/data_handler/policy_group_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/policy_group_data_handler.rb
@@ -1,4 +1,4 @@
-require "chef/chef_fs/data_handler/data_handler_base"
+require_relative "data_handler_base"
class Chef
module ChefFS
@@ -17,7 +17,7 @@ class Chef
# Verify that the JSON hash for this type has a key that matches its name.
#
- # @param object [Object] JSON hash of the object
+ # @param object_data [Object] JSON hash of the object
# @param entry [Chef::ChefFS::FileSystem::BaseFSObject] filesystem object we are verifying
# @yield [s] callback to handle errors
# @yieldparam [s<string>] error message
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 b09c146a5d..1007a29c8a 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_relative "data_handler_base"
+require_relative "../../role"
class Chef
module ChefFS
@@ -24,7 +24,7 @@ class Chef
end
def preserve_key?(key)
- return key == "name"
+ key == "name"
end
def chef_class
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 c2df4db49d..50a0c0682d 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_relative "data_handler_base"
class Chef
module ChefFS
@@ -19,7 +19,7 @@ class Chef
end
def preserve_key?(key)
- return key == "name"
+ 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 9c12bd4b96..37e72f379b 100644
--- a/lib/chef/chef_fs/file_pattern.rb
+++ b/lib/chef/chef_fs/file_pattern.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs"
-require "chef/chef_fs/path_utils"
+require_relative "../chef_fs"
+require_relative "path_utils"
class Chef
module ChefFS
@@ -74,6 +74,7 @@ class Chef
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
path_parts = Chef::ChefFS::PathUtils.split(path)
@@ -81,10 +82,11 @@ class Chef
return false if regexp_parts.length <= path_parts.length && !has_double_star
# If the path doesn't match up to this point, children won't match either.
return false if path_parts.zip(regexp_parts).any? { |part, regexp| !regexp.nil? && !regexp.match(part) }
+
# Otherwise, it's possible we could match: the path matches to this point, and the pattern is longer than the path.
# TODO There is one edge case where the double star comes after some characters like abc**def--we could check whether the next
# bit of path starts with abc in that case.
- return true
+ true
end
# Returns the immediate child of a path that would be matched
@@ -114,7 +116,8 @@ class Chef
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]
+
+ exact_parts[dirs_in_path]
end
# If this pattern represents an exact path, returns the exact path.
@@ -123,7 +126,8 @@ class Chef
# abc/*def.exact_path == 'abc/def'
# abc/x\\yz.exact_path == 'abc/xyz'
def exact_path
- return nil if has_double_star || exact_parts.any? { |part| part.nil? }
+ return nil if has_double_star || exact_parts.any?(&:nil?)
+
result = Chef::ChefFS::PathUtils.join(*exact_parts)
is_absolute ? Chef::ChefFS::PathUtils.join("", result) : result
end
@@ -151,6 +155,7 @@ class Chef
def match?(path)
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)
end
@@ -183,7 +188,7 @@ class Chef
end
def calculate
- if !@regexp
+ unless @regexp
@is_absolute = Chef::ChefFS::PathUtils.is_absolute?(@pattern)
full_regexp_parts = []
@@ -199,7 +204,7 @@ class Chef
end
# Skip // and /./ (pretend it's not there)
- if exact == "" || exact == "."
+ if ["", "."].include?(exact)
next
end
@@ -213,9 +218,10 @@ class Chef
if has_double_star_prev
raise ArgumentError, ".. overlapping a ** is unsupported"
end
+
full_regexp_parts.pop
normalized_parts.pop
- if !@has_double_star
+ unless @has_double_star
@regexp_parts.pop
@exact_parts.pop
end
@@ -226,7 +232,7 @@ class Chef
# Build up the regexp
full_regexp_parts << regexp
normalized_parts << part
- if !@has_double_star
+ unless @has_double_star
@regexp_parts << Regexp.new("^#{regexp}$")
@exact_parts << exact
end
@@ -239,7 +245,7 @@ class Chef
end
def self.pattern_special_characters
- if Chef::ChefFS.windows?
+ if ChefUtils.windows?
@pattern_special_characters ||= /(\*\*|\*|\?|[\*\?\.\|\(\)\[\]\{\}\+\\\\\^\$])/
else
# Unix also supports character regexes and backslashes
@@ -259,7 +265,7 @@ class Chef
pattern.split(pattern_special_characters).each_with_index do |part, index|
# Odd indexes from the split are symbols. Even are normal bits.
if index.even?
- exact << part if !exact.nil?
+ exact << part unless exact.nil?
regexp << part
else
case part
@@ -277,7 +283,7 @@ class Chef
else
if part[0, 1] == '\\' && part.length == 2
# backslash escapes are only supported on Unix, and are handled here by leaving the escape on (it means the same thing in a regex)
- exact << part[1, 1] if !exact.nil?
+ exact << part[1, 1] unless exact.nil?
if regexp_escape_characters.include?(part[1, 1])
regexp << part
else
@@ -288,7 +294,7 @@ class Chef
exact = nil
regexp << part
else
- exact += part if !exact.nil?
+ exact += part unless exact.nil?
regexp << "\\#{part}"
end
end
diff --git a/lib/chef/chef_fs/file_system.rb b/lib/chef/chef_fs/file_system.rb
index 1a8da2fd6b..73c3f0090a 100644
--- a/lib/chef/chef_fs/file_system.rb
+++ b/lib/chef/chef_fs/file_system.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/chef_fs/path_utils"
-require "chef/chef_fs/file_system/exceptions"
-require "chef/chef_fs/parallelizer"
+require_relative "path_utils"
+require_relative "file_system/exceptions"
+require_relative "parallelizer"
class Chef
module ChefFS
@@ -94,6 +94,7 @@ class Chef
def self.resolve_path(entry, path)
return entry if path.length == 0
return resolve_path(entry.root, path) if path[0, 1] == "/" && entry.root != entry
+
if path[0, 1] == "/"
path = path[1, path.length - 1]
end
@@ -194,7 +195,7 @@ class Chef
# Check the outer regex pattern to see if it matches anything on the
# filesystem that isn't on the server
Chef::ChefFS::FileSystem.list(b_root, pattern).each do |b|
- if !found_paths.include?(b.display_path)
+ unless found_paths.include?(b.display_path)
a = Chef::ChefFS::FileSystem.resolve_path(a_root, b.display_path)
yield [ a, b ]
end
@@ -228,7 +229,7 @@ class Chef
# Check b for children that aren't in a
b.children.each do |b_child|
- if !a_children_names.include?(b_child.bare_name)
+ unless a_children_names.include?(b_child.bare_name)
result << [ a.child(b_child.bare_name), b_child ]
end
end
@@ -291,7 +292,7 @@ class Chef
end
end
else
- ui.output ("Not deleting extra entry #{dest_path} (purge is off)") if ui
+ ui.output("Not deleting extra entry #{dest_path} (purge is off)") if ui
end
end
@@ -419,7 +420,7 @@ class Chef
ui.output "Created #{parent_path}" if ui
end
end
- return parent
+ parent
end
def parallel_do(enum, options = {}, &block)
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 2827e5b384..3805fa2bb0 100644
--- a/lib/chef/chef_fs/file_system/base_fs_dir.rb
+++ b/lib/chef/chef_fs/file_system/base_fs_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/file_system/base_fs_object"
-require "chef/chef_fs/file_system/nonexistent_fs_object"
+require_relative "base_fs_object"
+require_relative "nonexistent_fs_object"
class Chef
module ChefFS
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 9767b5b1ba..a0bcf3ff65 100644
--- a/lib/chef/chef_fs/file_system/base_fs_object.rb
+++ b/lib/chef/chef_fs/file_system/base_fs_object.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/path_utils"
-require "chef/chef_fs/file_system/exceptions"
+require_relative "../path_utils"
+require_relative "exceptions"
class Chef
module ChefFS
@@ -32,6 +32,7 @@ class Chef
if name != ""
raise ArgumentError, "Name of root object must be empty string: was '#{name}' instead"
end
+
@path = "/"
end
end
@@ -107,13 +108,15 @@ class Chef
# Override children to report your *actual* list of children as an array.
def children
- raise NotFoundError.new(self) if !exists?
+ raise NotFoundError.new(self) unless exists?
+
[]
end
# Expand this entry into a chef object (Chef::Role, ::Node, etc.)
def chef_object
- raise NotFoundError.new(self) if !exists?
+ raise NotFoundError.new(self) unless exists?
+
nil
end
@@ -124,14 +127,16 @@ class Chef
# your entry class, and will be called without actually reading the
# file_contents. This is used for knife upload /cookbooks/cookbookname.
def create_child(name, file_contents)
- raise NotFoundError.new(self) if !exists?
+ raise NotFoundError.new(self) unless exists?
+
raise OperationNotAllowedError.new(:create_child, self)
end
# Delete this item, possibly recursively. Entries MUST NOT delete a
# directory unless recurse is true.
def delete(recurse)
- raise NotFoundError.new(self) if !exists?
+ raise NotFoundError.new(self) unless exists?
+
raise OperationNotAllowedError.new(:delete, self)
end
@@ -166,13 +171,15 @@ class Chef
# Read the contents of this file entry.
def read
- raise NotFoundError.new(self) if !exists?
+ raise NotFoundError.new(self) unless exists?
+
raise OperationNotAllowedError.new(:read, self)
end
# Write the contents of this file entry.
def write(file_contents)
- raise NotFoundError.new(self) if !exists?
+ raise NotFoundError.new(self) unless exists?
+
raise OperationNotAllowedError.new(:write, self)
end
@@ -184,4 +191,4 @@ class Chef
end
end
-require "chef/chef_fs/file_system/nonexistent_fs_object"
+require_relative "nonexistent_fs_object"
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
index 006098a0c9..f9448c2fc1 100644
--- a/lib/chef/chef_fs/file_system/chef_server/acl_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_server/acl_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# 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/exceptions"
+require_relative "../base_fs_dir"
+require_relative "acl_entry"
+require_relative "../exceptions"
class Chef
module ChefFS
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
index f4655412fa..db29ce7ba7 100644
--- a/lib/chef/chef_fs/file_system/chef_server/acl_entry.rb
+++ b/lib/chef/chef_fs/file_system/chef_server/acl_entry.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require "chef/chef_fs/file_system/chef_server/rest_list_entry"
-require "chef/chef_fs/file_system/exceptions"
+require_relative "rest_list_entry"
+require_relative "../exceptions"
class Chef
module ChefFS
module FileSystem
module ChefServer
class AclEntry < RestListEntry
- PERMISSIONS = %w{create read update delete grant}
+ PERMISSIONS = %w{create read update delete grant}.freeze
def api_path
"#{super}/_acl"
@@ -47,17 +47,17 @@ class Chef
# 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
+
+ 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::HTTPClientException => 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
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
index b9af486203..9bc348482b 100644
--- a/lib/chef/chef_fs/file_system/chef_server/acls_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_server/acls_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/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/policies_acl_dir"
-require "chef/chef_fs/file_system/chef_server/acl_entry"
-require "chef/chef_fs/data_handler/acl_data_handler"
+require_relative "../base_fs_dir"
+require_relative "acl_dir"
+require_relative "cookbooks_acl_dir"
+require_relative "policies_acl_dir"
+require_relative "acl_entry"
+require_relative "../../data_handler/acl_data_handler"
class Chef
module ChefFS
module FileSystem
module ChefServer
class AclsDir < BaseFSDir
- ENTITY_TYPES = %w{clients containers cookbook_artifacts cookbooks data_bags environments groups nodes policies policy_groups roles} # we don't read sandboxes, so we don't read their acls
+ ENTITY_TYPES = %w{clients containers cookbook_artifacts cookbooks data_bags environments groups nodes policies policy_groups roles}.freeze # we don't read sandboxes, so we don't read their acls
def data_handler
@data_handler ||= Chef::ChefFS::DataHandler::AclDataHandler.new
@@ -60,7 +60,7 @@ class Chef
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
+ @children << AclEntry.new("organization.json", self, true) # the org acl is retrieved as GET /organizations/ORGNAME/ANYTHING/_acl
end
@children
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
index 5030a0733f..3477278c96 100644
--- 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
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,30 +16,30 @@
# 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"
+require_relative "../../../server_api"
+require_relative "acls_dir"
+require_relative "../base_fs_dir"
+require_relative "rest_list_dir"
+require_relative "cookbooks_dir"
+require_relative "cookbook_artifacts_dir"
+require_relative "versioned_cookbooks_dir"
+require_relative "data_bags_dir"
+require_relative "nodes_dir"
+require_relative "org_entry"
+require_relative "organization_invites_entry"
+require_relative "organization_members_entry"
+require_relative "policies_dir"
+require_relative "policy_groups_dir"
+require_relative "environments_dir"
+require_relative "../../data_handler/acl_data_handler"
+require_relative "../../data_handler/client_data_handler"
+require_relative "../../data_handler/environment_data_handler"
+require_relative "../../data_handler/node_data_handler"
+require_relative "../../data_handler/role_data_handler"
+require_relative "../../data_handler/user_data_handler"
+require_relative "../../data_handler/group_data_handler"
+require_relative "../../data_handler/container_data_handler"
+require_relative "../../data_handler/policy_group_data_handler"
class Chef
module ChefFS
@@ -99,15 +99,15 @@ class Chef
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")
+ 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)
+ chef_rest.get(path)
end
def chef_rest
- Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key)
+ Chef::ServerAPI.new(chef_server_url, client_name: chef_username, signing_key_filename: chef_private_key, api_version: "0")
end
def api_path
@@ -186,7 +186,7 @@ class Chef
RestListDir.new("users", self, nil, Chef::ChefFS::DataHandler::UserDataHandler.new),
]
end
- result.sort_by { |child| child.name }
+ result.sort_by(&:name)
end
end
end
diff --git a/lib/chef/chef_fs/file_system/chef_server/cookbook_artifact_dir.rb b/lib/chef/chef_fs/file_system/chef_server/cookbook_artifact_dir.rb
index faea96e944..936eedccfa 100644
--- a/lib/chef/chef_fs/file_system/chef_server/cookbook_artifact_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_server/cookbook_artifact_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/file_system/chef_server/cookbook_dir"
+require_relative "cookbook_dir"
class Chef
module ChefFS
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
index 0b82a64a0a..feda934b54 100644
--- 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
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/file_system/chef_server/cookbooks_dir"
-require "chef/chef_fs/file_system/chef_server/cookbook_artifact_dir"
+require_relative "cookbooks_dir"
+require_relative "cookbook_artifact_dir"
class Chef
module ChefFS
@@ -44,7 +44,7 @@ class Chef
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)
+ result << CookbookArtifactDir.new("#{cookbook_name}-#{cookbook_version["identifier"]}", self)
end
end
result.sort_by(&:name)
@@ -56,7 +56,7 @@ class Chef
# 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("-")
+ cookbook_name, _, identifier = other.name.rpartition("-")
Dir.mktmpdir do |temp_cookbooks_path|
proxy_cookbook_path = "#{temp_cookbooks_path}/#{cookbook_name}"
@@ -65,15 +65,15 @@ class Chef
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
+ proxy_loader = Chef::Cookbook::CookbookVersionLoader.new(proxy_cookbook_path, other.chefignore)
+ proxy_loader.load!
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)
+ uploader = Chef::CookbookUploader.new(cookbook_to_upload, force: options[:force], rest: chef_rest, policy_mode: true)
with_actual_cookbooks_dir(temp_cookbooks_path) do
uploader.upload_cookbooks
@@ -86,12 +86,16 @@ class Chef
# the symlink without removing the original contents if we
# are running on windows
#
- if Chef::Platform.windows?
+ if ChefUtils.windows?
Dir.rmdir proxy_cookbook_path
end
end
end
+ def chef_rest
+ Chef::ServerAPI.new(root.chef_rest.url, root.chef_rest.options.merge(version_class: Chef::CookbookManifestVersions))
+ end
+
def can_have_child?(name, is_dir)
is_dir && name.include?("-")
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
index 3fa5b49eb1..f724b8ab21 100644
--- a/lib/chef/chef_fs/file_system/chef_server/cookbook_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_server/cookbook_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,13 +16,13 @@
# 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/exceptions"
-require "chef/cookbook_version"
-require "chef/cookbook_uploader"
+require_relative "../../command_line"
+require_relative "rest_list_dir"
+require_relative "cookbook_subdir"
+require_relative "cookbook_file"
+require_relative "../exceptions"
+require_relative "../../../cookbook_version"
+require_relative "../../../cookbook_uploader"
class Chef
module ChefFS
@@ -49,18 +49,6 @@ class Chef
attr_reader :cookbook_name, :version
- COOKBOOK_SEGMENT_INFO = {
- :attributes => { :ruby_only => true },
- :definitions => { :ruby_only => true },
- :recipes => { :ruby_only => true },
- :libraries => { :recursive => 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
@@ -73,45 +61,40 @@ class Chef
# 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.find { |child| child.name == name }
- rescue Chef::ChefFS::FileSystem::NotFoundError
- nil
- end
+
+ children.find { |child| child.name == name }
+ rescue Chef::ChefFS::FileSystem::NotFoundError
+ nil
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
+ return name != "root_files" if is_dir
+
+ 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("/")
+ manifest = chef_object.cookbook_manifest
+ manifest.by_parent_directory.each_value do |files|
+ files.each do |file|
+ parts = 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.find { |child| part == child.name }
- if !container
- container = CookbookSubdir.new(part, old_container, segment_info[:ruby_only], segment_info[:recursive])
+ unless container
+ container = CookbookSubdir.new(part, old_container, false, true)
old_container.add_child(container)
end
end
# Create the file itself
- container.add_child(CookbookFile.new(parts[parts.length - 1], container, segment_file))
+ container.add_child(CookbookFile.new(parts[parts.length - 1], container, file))
end
end
- @children = @children.sort_by { |c| c.name }
+ @children = @children.sort_by(&:name)
end
@children
end
@@ -126,7 +109,7 @@ class Chef
rest.delete(api_path)
rescue Timeout::Error => e
raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "Timeout deleting: #{e}")
- rescue Net::HTTPServerException
+ rescue Net::HTTPClientException
if $!.response.code == "404"
raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
else
@@ -134,7 +117,8 @@ class Chef
end
end
else
- raise NotFoundError.new(self) if !exists?
+ raise NotFoundError.new(self) unless exists?
+
raise MustDeleteRecursivelyError.new(self, "#{path_for_printing} must be deleted recursively")
end
end
@@ -149,12 +133,13 @@ class Chef
end
def compare_to(other)
- if !other.dir?
+ unless 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)
+ if %i{directory_to_file file_to_directory deleted added modified}.include?(type)
are_same = false
end
end
@@ -166,7 +151,11 @@ class Chef
end
def rest
- parent.rest
+ Chef::ServerAPI.new(parent.rest.url, parent.rest.options.merge(version_class: Chef::CookbookManifestVersions))
+ end
+
+ def chef_rest
+ Chef::ServerAPI.new(parent.chef_rest.url, parent.chef_rest.options.merge(version_class: Chef::CookbookManifestVersions))
end
def chef_object
@@ -188,7 +177,7 @@ class Chef
old_retry_count = Chef::Config[:http_retry_count]
begin
Chef::Config[:http_retry_count] = 0
- @chef_object ||= Chef::CookbookVersion.from_hash(root.get_json(api_path))
+ @chef_object ||= Chef::CookbookVersion.from_hash(chef_rest.get(api_path))
ensure
Chef::Config[:http_retry_count] = old_retry_count
end
@@ -196,7 +185,7 @@ class Chef
rescue Timeout::Error => e
raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "Timeout reading: #{e}")
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => e
if e.response.code == "404"
@could_not_get_chef_object = e
raise Chef::ChefFS::FileSystem::NotFoundError.new(self, @could_not_get_chef_object)
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
index 426cc62039..c49ed51d81 100644
--- a/lib/chef/chef_fs/file_system/chef_server/cookbook_file.rb
+++ b/lib/chef/chef_fs/file_system/chef_server/cookbook_file.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/chef_fs/file_system/base_fs_object"
-require "chef/http/simple"
-require "openssl"
+require_relative "../base_fs_object"
+require_relative "../../../http/simple"
+require "digest" unless defined?(Digest)
class Chef
module ChefFS
@@ -37,20 +37,14 @@ class Chef
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
+ tmpfile = rest.streaming_request(file[:url])
+ File.open(tmpfile, "rb", &:read)
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "Timeout reading #{file[:url]}: #{e}")
+ rescue Net::HTTPClientException => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "#{e.message} retrieving #{file[:url]}")
+ rescue Errno::ENOENT
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
end
def rest
@@ -75,7 +69,7 @@ class Chef
private
def calc_checksum(value)
- OpenSSL::Digest::MD5.hexdigest(value)
+ ::Digest::MD5.hexdigest(value)
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
index 01297a39ba..91b5e47d66 100644
--- a/lib/chef/chef_fs/file_system/chef_server/cookbook_subdir.rb
+++ b/lib/chef/chef_fs/file_system/chef_server/cookbook_subdir.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/file_system/base_fs_dir"
+require_relative "../base_fs_dir"
class Chef
module ChefFS
@@ -39,7 +39,7 @@ class Chef
def can_have_child?(name, is_dir)
if is_dir
- return false if !@recursive
+ return false unless @recursive
else
return false if @ruby_only && name !~ /\.rb$/
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
index e020d0fb34..c2430c6ba5 100644
--- 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
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/file_system/chef_server/acl_dir"
+require_relative "acl_dir"
class Chef
module ChefFS
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
index 631562d7ef..e9f973ef6f 100644
--- a/lib/chef/chef_fs/file_system/chef_server/cookbooks_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_server/cookbooks_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,13 +16,13 @@
# 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/exceptions"
-require "chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir"
-require "chef/mixin/file_class"
+require_relative "rest_list_dir"
+require_relative "cookbook_dir"
+require_relative "../exceptions"
+require_relative "../repository/chef_repository_file_system_cookbook_dir"
+require_relative "../../../mixin/file_class"
-require "tmpdir"
+require "tmpdir" unless defined?(Dir.mktmpdir)
class Chef
module ChefFS
@@ -60,7 +60,7 @@ class Chef
upload_cookbook(other, options)
rescue Timeout::Error => e
raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "Timeout writing: #{e}")
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => e
case e.response.code
when "409"
raise Chef::ChefFS::FileSystem::CookbookFrozenError.new(:write, self, e, "Cookbook #{other.name} is frozen")
@@ -72,19 +72,36 @@ class Chef
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)
+ cookbook = other.chef_object if other.chef_object
+ raise Chef::Exceptions::MetadataNotFound.new(cookbook.root_paths[0], cookbook.name) unless cookbook.has_metadata_file?
- with_actual_cookbooks_dir(other.parent.file_path) do
- uploader.upload_cookbooks
+ if cookbook
+ Chef::CookbookLoader.copy_to_tmp_dir_from_array([cookbook]) do |tmp_cl|
+ tmp_cl.load_cookbooks
+ tmp_cl.compile_metadata
+ tmp_cl.freeze_versions if options[:freeze]
+ cookbook_for_upload = []
+ tmp_cl.each do |cookbook_name, cookbook|
+ cookbook_for_upload << cookbook
+ end
+
+ uploader = Chef::CookbookUploader.new(cookbook_for_upload, force: options[:force], rest: chef_rest)
+
+ with_actual_cookbooks_dir(other.parent.file_path) do
+ uploader.upload_cookbooks
+ end
+ end
end
end
+ def chef_rest
+ Chef::ServerAPI.new(root.chef_rest.url, root.chef_rest.options.merge(version_class: Chef::CookbookManifestVersions))
+ 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
+ Chef::Config.cookbook_path = actual_cookbook_path unless Chef::Config.cookbook_path
yield
ensure
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
index ee0ecd3b40..399d5af738 100644
--- 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
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,10 +16,10 @@
# 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_entry"
-require "chef/chef_fs/file_system/exceptions"
-require "chef/chef_fs/data_handler/data_bag_item_data_handler"
+require_relative "rest_list_dir"
+require_relative "data_bag_entry"
+require_relative "../exceptions"
+require_relative "../../data_handler/data_bag_item_data_handler"
class Chef
module ChefFS
@@ -48,15 +48,16 @@ class Chef
end
def delete(recurse)
- if !recurse
- raise NotFoundError.new(self) if !exists?
+ unless recurse
+ raise NotFoundError.new(self) unless 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
+ rescue Net::HTTPClientException => e
if e.response.code == "404"
raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
else
diff --git a/lib/chef/chef_fs/file_system/chef_server/data_bag_entry.rb b/lib/chef/chef_fs/file_system/chef_server/data_bag_entry.rb
index c0093058b7..19e0fb409e 100644
--- a/lib/chef/chef_fs/file_system/chef_server/data_bag_entry.rb
+++ b/lib/chef/chef_fs/file_system/chef_server/data_bag_entry.rb
@@ -1,5 +1,5 @@
-require "chef/chef_fs/file_system/chef_server/rest_list_entry"
-require "chef/chef_fs/data_handler/data_bag_item_data_handler"
+require_relative "rest_list_entry"
+require_relative "../../data_handler/data_bag_item_data_handler"
class Chef
module ChefFS
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
index ec382e60ef..1e824c155c 100644
--- 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
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/file_system/chef_server/rest_list_dir"
-require "chef/chef_fs/file_system/chef_server/data_bag_dir"
+require_relative "rest_list_dir"
+require_relative "data_bag_dir"
class Chef
module ChefFS
@@ -30,16 +30,14 @@ class Chef
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
+ @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::HTTPClientException => 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
@@ -52,7 +50,7 @@ class Chef
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
+ rescue Net::HTTPClientException => e
if e.response.code == "409"
raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e, "Cannot create #{name} under #{path}: already exists")
else
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
index e4714cf089..8366d9d326 100644
--- a/lib/chef/chef_fs/file_system/chef_server/environments_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_server/environments_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# 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/exceptions"
+require_relative "../base_fs_dir"
+require_relative "rest_list_entry"
+require_relative "../exceptions"
class Chef
module ChefFS
@@ -40,12 +40,14 @@ class Chef
end
def delete(recurse)
- raise NotFoundError.new(self) if !exists?
+ raise NotFoundError.new(self) unless exists?
+
raise DefaultEnvironmentCannotBeModifiedError.new(:delete, self)
end
def write(file_contents)
- raise NotFoundError.new(self) if !exists?
+ raise NotFoundError.new(self) unless exists?
+
raise DefaultEnvironmentCannotBeModifiedError.new(:write, self)
end
end
diff --git a/lib/chef/chef_fs/file_system/chef_server/nodes_dir.rb b/lib/chef/chef_fs/file_system/chef_server/nodes_dir.rb
index df2388f1df..81d6f91f33 100644
--- a/lib/chef/chef_fs/file_system/chef_server/nodes_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_server/nodes_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,10 +16,10 @@
# 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/exceptions"
-require "chef/chef_fs/data_handler/node_data_handler"
+require_relative "../base_fs_dir"
+require_relative "rest_list_entry"
+require_relative "../exceptions"
+require_relative "../../data_handler/node_data_handler"
class Chef
module ChefFS
@@ -28,18 +28,16 @@ class Chef
class NodesDir < RestListDir
# 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, 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
+ @children ||= root.get_json(env_api_path).keys.sort.map do |key|
+ make_child_entry(key, true)
+ end
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "Timeout retrieving children: #{e}")
+ rescue Net::HTTPClientException => 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
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
index 7253de3449..f2dfc944cd 100644
--- a/lib/chef/chef_fs/file_system/chef_server/org_entry.rb
+++ b/lib/chef/chef_fs/file_system/chef_server/org_entry.rb
@@ -1,5 +1,5 @@
-require "chef/chef_fs/file_system/chef_server/rest_list_entry"
-require "chef/chef_fs/data_handler/organization_data_handler"
+require_relative "rest_list_entry"
+require_relative "../../data_handler/organization_data_handler"
class Chef
module ChefFS
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
index adaffb99a7..d852a3bc2e 100644
--- 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
@@ -1,6 +1,6 @@
-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"
+require_relative "rest_list_entry"
+require_relative "../../data_handler/organization_invites_data_handler"
+require_relative "../../../json_compat"
class Chef
module ChefFS
@@ -40,19 +40,19 @@ class Chef
end
def write(contents)
- desired_invites = minimize_value(Chef::JSONCompat.parse(contents, :create_additions => false))
+ 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
+
+ rest.post(api_path, { "user" => invite })
+ rescue Net::HTTPClientException => 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
(invites - desired_invites).each do |invite|
rest.delete(File.join(api_path, actual_invites[invite]))
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
index 7e9c7141c4..ded890cc32 100644
--- 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
@@ -1,6 +1,6 @@
-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"
+require_relative "rest_list_entry"
+require_relative "../../data_handler/organization_members_data_handler"
+require_relative "../../../json_compat"
class Chef
module ChefFS
@@ -40,18 +40,18 @@ class Chef
end
def write(contents)
- desired_members = minimize_value(Chef::JSONCompat.parse(contents, :create_additions => false))
+ 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
+
+ rest.post(api_path, "username" => member)
+ rescue Net::HTTPClientException => 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
(members - desired_members).each do |member|
rest.delete(File.join(api_path, member))
diff --git a/lib/chef/chef_fs/file_system/chef_server/policies_acl_dir.rb b/lib/chef/chef_fs/file_system/chef_server/policies_acl_dir.rb
index fa1d184b7d..ba036c37d6 100644
--- a/lib/chef/chef_fs/file_system/chef_server/policies_acl_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_server/policies_acl_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/file_system/chef_server/acl_dir"
+require_relative "acl_dir"
class Chef
module ChefFS
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
index 4a4be19fe4..aa6981f23c 100644
--- a/lib/chef/chef_fs/file_system/chef_server/policies_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_server/policies_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/file_system/chef_server/rest_list_dir"
-require "chef/chef_fs/file_system/chef_server/policy_revision_entry"
+require_relative "rest_list_dir"
+require_relative "policy_revision_entry"
class Chef
module ChefFS
@@ -66,42 +66,41 @@ class Chef
# }
# }
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
+ # 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"].each_key do |policy_revision|
+ filename = "#{policy_name}-#{policy_revision}.json"
+ result << make_child_entry(filename, true)
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}")
+ result
+ end
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "Timeout retrieving children: #{e}")
+ rescue Net::HTTPClientException => 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 /organizations/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::HTTPClientException => e
+ if e.response.code == "404"
+ 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
+ # Anything else is unexpected (OperationFailedError)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}")
end
end
@@ -133,7 +132,7 @@ class Chef
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
+ rescue Net::HTTPClientException => e
# 404 = NotFoundError
if e.response.code == "404"
raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
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
index b7413c44c5..2c2707b239 100644
--- 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
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/file_system/exceptions"
+require_relative "../exceptions"
class Chef
module ChefFS
@@ -77,11 +77,12 @@ class Chef
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_data =
+ begin
+ rest.get(policy_path)
+ rescue Net::HTTPClientException => 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)
@@ -93,7 +94,7 @@ class Chef
end
begin
- existing_group = Chef::JSONCompat.parse(self.read)
+ existing_group = Chef::JSONCompat.parse(read)
rescue NotFoundError
# It's OK if the group doesn't already exist, just means no existing policies
end
@@ -113,7 +114,7 @@ class Chef
rescue Timeout::Error => e
raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Timeout creating '#{name}': #{e}")
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => e
# 404 = NotFoundError
if e.response.code == "404"
raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
diff --git a/lib/chef/chef_fs/file_system/chef_server/policy_groups_dir.rb b/lib/chef/chef_fs/file_system/chef_server/policy_groups_dir.rb
index 0452fa4573..37e5080e6a 100644
--- a/lib/chef/chef_fs/file_system/chef_server/policy_groups_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_server/policy_groups_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,10 +16,10 @@
# 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/exceptions"
-require "chef/chef_fs/file_system/chef_server/policy_group_entry"
+require_relative "../base_fs_dir"
+require_relative "rest_list_entry"
+require_relative "../exceptions"
+require_relative "policy_group_entry"
class Chef
module ChefFS
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
index 325b18e429..9b99fea730 100644
--- 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
@@ -1,5 +1,5 @@
-require "chef/chef_fs/file_system/chef_server/rest_list_entry"
-require "chef/chef_fs/data_handler/policy_data_handler"
+require_relative "rest_list_entry"
+require_relative "../../data_handler/policy_data_handler"
class Chef
module ChefFS
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
index dfd26a0241..0a1ee83c36 100644
--- 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
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# 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/exceptions"
+require_relative "../base_fs_dir"
+require_relative "rest_list_entry"
+require_relative "../exceptions"
class Chef
module ChefFS
@@ -71,40 +71,39 @@ class Chef
# 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, 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 /organizations/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}")
+ # 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, true)
+ end
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "Timeout retrieving children: #{e}")
+ rescue Net::HTTPClientException => 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 /organizations/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::HTTPClientException => e
+ if e.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
end
- else
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- end
- # Anything else is unexpected (OperationFailedError)
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}")
+ end
else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}")
+ 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
@@ -135,7 +134,7 @@ class Chef
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
+ rescue Net::HTTPClientException => e
# 404 = NotFoundError
if e.response.code == "404"
raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
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
index b8ec5f8524..bd0d400723 100644
--- 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
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,11 +16,11 @@
# limitations under the License.
#
-require "chef/chef_fs/file_system/base_fs_object"
-require "chef/chef_fs/file_system/exceptions"
-require "chef/role"
-require "chef/node"
-require "chef/json_compat"
+require_relative "../base_fs_object"
+require_relative "../exceptions"
+require_relative "../../../role"
+require_relative "../../../node"
+require_relative "../../../json_compat"
class Chef
module ChefFS
@@ -30,6 +30,7 @@ class Chef
def initialize(name, parent, exists = nil)
super(name, parent)
@exists = exists
+ @this_object_cache = nil
end
def data_handler
@@ -69,7 +70,14 @@ class Chef
def exists?
if @exists.nil?
begin
- @exists = parent.children.any? { |child| child.api_child_name == api_child_name }
+ @this_object_cache = rest.get(api_path)
+ @exists = true
+ rescue Net::HTTPClientException => e
+ if e.response.code == "404"
+ @exists = false
+ else
+ raise
+ end
rescue Chef::ChefFS::FileSystem::NotFoundError
@exists = false
end
@@ -78,35 +86,33 @@ class Chef
end
def delete(recurse)
- begin
- rest.delete(api_path)
- rescue Timeout::Error => e
+ # free up cache - it will be hydrated on next check for exists?
+ @this_object_cache = nil
+ rest.delete(api_path)
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "Timeout deleting: #{e}")
+ rescue Net::HTTPClientException => 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}")
- 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))
+ # Minimize the value (get rid of defaults) so the results don't look terrible
+ Chef::JSONCompat.to_json_pretty(normalize_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
+ @this_object_cache ? JSON.parse(@this_object_cache) : root.get_json(api_path)
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "Timeout reading: #{e}")
+ rescue Net::HTTPClientException => 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
@@ -116,7 +122,11 @@ class Chef
end
def minimize_value(value)
- data_handler.minimize(data_handler.normalize(value, self), self)
+ data_handler.minimize(normalize_value(value), self)
+ end
+
+ def normalize_value(value)
+ data_handler.normalize(value, self)
end
def compare_to(other)
@@ -148,6 +158,9 @@ class Chef
other_value = minimize_value(other_value)
other_value_json = Chef::JSONCompat.to_json_pretty(other_value)
+ # free up cache - it will be hydrated on next check for exists?
+ @this_object_cache = nil
+
[ value == other_value, value_json, other_value_json ]
end
@@ -156,6 +169,9 @@ class Chef
end
def write(file_contents)
+ # free up cache - it will be hydrated on next check for exists?
+ @this_object_cache = nil
+
begin
object = Chef::JSONCompat.parse(file_contents)
rescue Chef::Exceptions::JSON::ParseError => e
@@ -165,7 +181,7 @@ class Chef
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}")
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, nil, error.to_s)
end
end
@@ -173,7 +189,7 @@ class Chef
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
+ rescue Net::HTTPClientException => e
if e.response.code == "404"
raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
else
@@ -183,11 +199,9 @@ class Chef
end
def api_error_text(response)
- begin
- Chef::JSONCompat.parse(response.body)["error"].join("\n")
- rescue
- response.body
- end
+ Chef::JSONCompat.parse(response.body)["error"].join("\n")
+ rescue
+ response.body
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
index 269e160d43..84a331f2a0 100644
--- 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
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/file_system/chef_server/cookbook_dir"
+require_relative "cookbook_dir"
class Chef
module ChefFS
@@ -24,8 +24,8 @@ class Chef
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+)$/
+ # https://github.com/chef/chef_objects/blob/968a63344d38fd507f6ace05f73d53e9cd7fb043/src/chef_regex.erl#L94
+ VALID_VERSIONED_COOKBOOK_NAME = /^([.a-zA-Z0-9_-]+)-(\d+\.\d+\.\d+)$/.freeze
def initialize(name, parent, options = {})
super(name, parent)
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
index 172405763a..4f8aca0d23 100644
--- 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
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/file_system/chef_server/cookbooks_dir"
-require "chef/chef_fs/file_system/chef_server/versioned_cookbook_dir"
+require_relative "cookbooks_dir"
+require_relative "versioned_cookbook_dir"
class Chef
module ChefFS
@@ -50,7 +50,7 @@ class Chef
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)
+ result << VersionedCookbookDir.new("#{cookbook_name}-#{cookbook_version["version"]}", self)
end
end
result.sort_by(&:name)
@@ -71,14 +71,14 @@ class Chef
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
+ proxy_loader = Chef::Cookbook::CookbookVersionLoader.new(proxy_cookbook_path, other.chefignore)
+ proxy_loader.load!
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)
+ uploader = Chef::CookbookUploader.new(cookbook_to_upload, force: options[:force], rest: chef_rest)
with_actual_cookbooks_dir(temp_cookbooks_path) do
uploader.upload_cookbooks
@@ -91,12 +91,16 @@ class Chef
# the symlink without removing the original contents if we
# are running on windows
#
- if Chef::Platform.windows?
+ if ChefUtils.windows?
Dir.rmdir proxy_cookbook_path
end
end
end
+ def chef_rest
+ Chef::ServerAPI.new(root.chef_rest.url, root.chef_rest.options.merge(version_class: Chef::CookbookManifestVersions))
+ end
+
def can_have_child?(name, is_dir)
is_dir && name =~ Chef::ChefFS::FileSystem::ChefServer::VersionedCookbookDir::VALID_VERSIONED_COOKBOOK_NAME
end
diff --git a/lib/chef/chef_fs/file_system/exceptions.rb b/lib/chef/chef_fs/file_system/exceptions.rb
index 2a1baba8f5..8f6ce64f02 100644
--- a/lib/chef/chef_fs/file_system/exceptions.rb
+++ b/lib/chef/chef_fs/file_system/exceptions.rb
@@ -1,6 +1,6 @@
#
# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/lib/chef/chef_fs/file_system/memory/memory_dir.rb b/lib/chef/chef_fs/file_system/memory/memory_dir.rb
index 6049f404b1..e52a4b7c0f 100644
--- a/lib/chef/chef_fs/file_system/memory/memory_dir.rb
+++ b/lib/chef/chef_fs/file_system/memory/memory_dir.rb
@@ -1,5 +1,5 @@
-require "chef/chef_fs/file_system/base_fs_dir"
-require "chef/chef_fs/file_system/memory/memory_file"
+require_relative "../base_fs_dir"
+require_relative "memory_file"
class Chef
module ChefFS
@@ -38,7 +38,7 @@ class Chef
dir = self
path_parts.each do |path_part|
subdir = dir.child(path_part)
- if !subdir.exists?
+ unless subdir.exists?
subdir = MemoryDir.new(path_part, dir)
dir.add_child(subdir)
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
index 7eabc8fcb1..9201893dda 100644
--- a/lib/chef/chef_fs/file_system/memory/memory_file.rb
+++ b/lib/chef/chef_fs/file_system/memory/memory_file.rb
@@ -1,4 +1,4 @@
-require "chef/chef_fs/file_system/base_fs_object"
+require_relative "../base_fs_object"
class Chef
module ChefFS
@@ -11,7 +11,7 @@ class Chef
end
def read
- return @value
+ @value
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
index 4881b3d951..4a8b281389 100644
--- a/lib/chef/chef_fs/file_system/memory/memory_root.rb
+++ b/lib/chef/chef_fs/file_system/memory/memory_root.rb
@@ -1,4 +1,4 @@
-require "chef/chef_fs/file_system/memory/memory_dir"
+require_relative "memory_dir"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/multiplexed_dir.rb b/lib/chef/chef_fs/file_system/multiplexed_dir.rb
index 21abc012f8..82fa146264 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_relative "base_fs_object"
+require_relative "nonexistent_fs_object"
class Chef
module ChefFS
@@ -17,22 +17,20 @@ class Chef
end
def children
- begin
- result = []
- seen = {}
- # If multiple things have the same name, the first one wins.
- multiplexed_dirs.each do |dir|
- dir.children.each do |child|
- if seen[child.name]
- Chef::Log.warn("Child with name '#{child.name}' found in multiple directories: #{seen[child.name].path_for_printing} and #{child.path_for_printing}") unless seen[child.name].path_for_printing == child.path_for_printing
- else
- result << child
- seen[child.name] = child
- end
+ result = []
+ seen = {}
+ # If multiple things have the same name, the first one wins.
+ multiplexed_dirs.each do |dir|
+ dir.children.each do |child|
+ if seen[child.name]
+ Chef::Log.warn("Child with name '#{child.name}' found in multiple directories: #{seen[child.name].path_for_printing} and #{child.path_for_printing}") unless seen[child.name].path_for_printing == child.path_for_printing
+ else
+ result << child
+ seen[child.name] = child
end
end
- result
end
+ result
end
def make_child_entry(name)
@@ -41,7 +39,7 @@ class Chef
child_entry = dir.child(name)
if child_entry.exists?
if result
- Chef::Log.debug("Child with name '#{child_entry.name}' found in multiple directories: #{result.parent.path_for_printing} and #{child_entry.parent.path_for_printing}")
+ Chef::Log.trace("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
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 1a48d23047..3a30f68665 100644
--- a/lib/chef/chef_fs/file_system/nonexistent_fs_object.rb
+++ b/lib/chef/chef_fs/file_system/nonexistent_fs_object.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/file_system/base_fs_object"
-require "chef/chef_fs/file_system/exceptions"
+require_relative "base_fs_object"
+require_relative "exceptions"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/acl.rb b/lib/chef/chef_fs/file_system/repository/acl.rb
index 023ae11379..cfeb5fd3c9 100644
--- a/lib/chef/chef_fs/file_system/repository/acl.rb
+++ b/lib/chef/chef_fs/file_system/repository/acl.rb
@@ -1,6 +1,6 @@
#
# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/data_handler/acl_data_handler"
-require "chef/chef_fs/file_system/repository/base_file"
+require_relative "../../data_handler/acl_data_handler"
+require_relative "base_file"
class Chef
module ChefFS
@@ -32,7 +32,7 @@ class Chef
end
def bare_name
- if name == "organization" && parent.kind_of?(AclDir)
+ if name == "organization" && parent.is_a?(AclDir)
"organization.json"
else
name
diff --git a/lib/chef/chef_fs/file_system/repository/acls_dir.rb b/lib/chef/chef_fs/file_system/repository/acls_dir.rb
index 110befdf22..4a5bc03f15 100644
--- a/lib/chef/chef_fs/file_system/repository/acls_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/acls_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,11 +16,11 @@
# limitations under the License.
#
-require "chef/chef_fs/file_system/repository/acl"
-require "chef/chef_fs/file_system/repository/directory"
-require "chef/chef_fs/file_system/repository/acls_sub_dir"
-require "chef/chef_fs/file_system/chef_server/acls_dir"
-require "chef/chef_fs/data_handler/acl_data_handler"
+require_relative "acl"
+require_relative "directory"
+require_relative "acls_sub_dir"
+require_relative "../chef_server/acls_dir"
+require_relative "../../data_handler/acl_data_handler"
class Chef
module ChefFS
@@ -28,7 +28,7 @@ class Chef
module Repository
class AclsDir < Repository::Directory
- BARE_FILES = %w{ organization.json root }
+ BARE_FILES = %w{ organization.json root }.freeze
def can_have_child?(name, is_dir)
is_dir ? Chef::ChefFS::FileSystem::ChefServer::AclsDir::ENTITY_TYPES.include?(name) : BARE_FILES.include?(name)
diff --git a/lib/chef/chef_fs/file_system/repository/acls_sub_dir.rb b/lib/chef/chef_fs/file_system/repository/acls_sub_dir.rb
index 70c7d77480..c239b588d1 100644
--- a/lib/chef/chef_fs/file_system/repository/acls_sub_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/acls_sub_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: Jordan Running (<jr@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/chef_fs/file_system/repository/acl"
-require "chef/chef_fs/data_handler/acl_data_handler"
-require "chef/chef_fs/file_system/repository/directory"
+require_relative "acl"
+require_relative "../../data_handler/acl_data_handler"
+require_relative "directory"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/base_file.rb b/lib/chef/chef_fs/file_system/repository/base_file.rb
index 3e1edc8d62..94b52b9201 100644
--- a/lib/chef/chef_fs/file_system/repository/base_file.rb
+++ b/lib/chef/chef_fs/file_system/repository/base_file.rb
@@ -1,6 +1,6 @@
#
# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/file_system_cache"
+require_relative "../../file_system_cache"
class Chef
module ChefFS
@@ -43,12 +43,12 @@ class Chef
file_path = "#{parent.file_path}/#{name}"
- Chef::Log.debug "BaseFile: Detecting file extension for #{name}"
+ Chef::Log.trace "BaseFile: Detecting file extension for #{name}"
ext = File.exist?(file_path + ".rb") ? ".rb" : ".json"
name += ext
file_path += ext
- Chef::Log.debug "BaseFile: got a file path of #{file_path} for #{name}"
+ Chef::Log.trace "BaseFile: got a file path of #{file_path} for #{name}"
@name = name
@path = Chef::ChefFS::PathUtils.join(parent.path, name)
@file_path = file_path
@@ -92,6 +92,7 @@ class Chef
end
attr_writer :write_pretty_json
+
def write_pretty_json
@write_pretty_json.nil? ? root.write_pretty_json : @write_pretty_json
end
@@ -122,7 +123,7 @@ class Chef
if is_ruby_file?
data_handler.from_ruby(file_path).to_json
else
- File.open(file_path, "rb") { |f| f.read }
+ File.open(file_path, "rb", &:read)
end
rescue Errno::ENOENT
raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
@@ -132,6 +133,7 @@ class Chef
if is_ruby_file?
raise Chef::ChefFS::FileSystem::RubyFileError.new(:write, self)
end
+
if content && write_pretty_json && is_json_file?
content = minimize(content, self)
end
diff --git a/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_artifact_dir.rb b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_artifact_dir.rb
index 83c13e5e20..2cbbd466c3 100644
--- a/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_artifact_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_artifact_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir"
+require_relative "chef_repository_file_system_cookbook_dir"
class Chef
module ChefFS
@@ -25,11 +25,11 @@ class Chef
class ChefRepositoryFileSystemCookbookArtifactDir < ChefRepositoryFileSystemCookbookDir
# Override from parent
def cookbook_version
- loader = Chef::Cookbook::CookbookVersionLoader.new(file_path, parent.chefignore)
+ loader = Chef::Cookbook::CookbookVersionLoader.new(file_path, 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
+ loader.load!
cookbook_version = loader.cookbook_version
cookbook_version.identifier = identifier
cookbook_version
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
index 1b640bc076..0bb1ba1cac 100644
--- 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
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,11 +16,12 @@
# 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/exceptions"
-require "chef/cookbook/cookbook_version_loader"
+require_relative "chef_repository_file_system_cookbook_entry"
+require_relative "../chef_server/cookbook_dir"
+require_relative "../chef_server/versioned_cookbook_dir"
+require_relative "../exceptions"
+require_relative "../../../cookbook/cookbook_version_loader"
+require_relative "../../../cookbook/chefignore"
class Chef
module ChefFS
@@ -30,10 +31,16 @@ class Chef
# Represents ROOT/cookbooks/:cookbook
class ChefRepositoryFileSystemCookbookDir < ChefRepositoryFileSystemCookbookEntry
- # API Required by Respository::Directory
+ # API Required by Repository::Directory
+ def chefignore
+ @chefignore ||= Chef::Cookbook::Chefignore.new(file_path)
+ rescue Errno::EISDIR, Errno::EACCES
+ # Work around a bug in Chefignore when chefignore is a directory
+ end
def fs_entry_valid?
return false unless File.directory?(file_path) && name_valid?
+
if can_upload?
true
else
@@ -54,6 +61,7 @@ class Chef
if exists?
raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self)
end
+
begin
Dir.mkdir(file_path)
rescue Errno::EEXIST
@@ -64,11 +72,11 @@ class Chef
def write(cookbook_path, cookbook_version_json, from_fs)
# 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, self, nil, { :purge => true })
+ Chef::ChefFS::FileSystem.copy_to(Chef::ChefFS::FilePattern.new("/#{cookbook_path}"), from_fs, self, nil, { purge: true })
# Write out .uploaded-cookbook-version.json
# cookbook_file_path = File.join(file_path, cookbook_name) <- this should be the same as self.file_path
- if !File.exists?(file_path)
+ unless File.exist?(file_path)
FileUtils.mkdir_p(file_path)
end
uploaded_cookbook_version_path = File.join(file_path, Chef::Cookbook::CookbookVersionLoader::UPLOADED_COOKBOOK_VERSION_FILE)
@@ -80,18 +88,16 @@ class Chef
# Customizations of base class
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
+ cb = cookbook_version
+ unless 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
def children
@@ -99,12 +105,13 @@ class Chef
end
def can_have_child?(name, is_dir)
- if is_dir
+ if is_dir && !%w{ root_files .. . }.include?(name)
# Only the given directories will be uploaded.
- return Chef::ChefFS::FileSystem::ChefServer::CookbookDir::COOKBOOK_SEGMENT_INFO.keys.include?(name.to_sym) && name != "root_files"
+ return true
elsif name == Chef::Cookbook::CookbookVersionLoader::UPLOADED_COOKBOOK_VERSION_FILE
return false
end
+
super(name, is_dir)
end
@@ -112,7 +119,8 @@ class Chef
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]
+
+ name_match[1]
end
def canonical_cookbook_name(entry_name)
@@ -124,19 +132,18 @@ class Chef
end
def can_upload?
- File.exists?(uploaded_cookbook_version_path) || children.size > 0
+ File.exist?(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])
+ ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, false, true)
end
def cookbook_version
- loader = Chef::Cookbook::CookbookVersionLoader.new(file_path, parent.chefignore)
- loader.load_cookbooks
+ loader = Chef::Cookbook::CookbookVersionLoader.new(file_path, chefignore)
+ loader.load!
loader.cookbook_version
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
index 4019c6985b..d28bb65db6 100644
--- 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
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/chef_fs/file_system/repository/file_system_entry"
-require "chef/chef_fs/file_system/repository/cookbooks_dir"
-require "chef/chef_fs/file_system/exceptions"
+require_relative "file_system_entry"
+require_relative "cookbooks_dir"
+require_relative "../exceptions"
class Chef
module ChefFS
@@ -52,31 +52,30 @@ class Chef
end
def children
- begin
- entries = Dir.entries(file_path).sort.
- map { |child_name| make_child_entry(child_name) }.
- select { |child| child && can_have_child?(child.name, child.dir?) }
- entries.select { |entry| !(entry.dir? && entry.children.size == 0 ) }
- rescue Errno::ENOENT
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- end
+ entries = Dir.entries(file_path).sort
+ .map { |child_name| make_child_entry(child_name) }
+ .select { |child| child && can_have_child?(child.name, child.dir?) }
+ entries.select { |entry| !(entry.dir? && entry.children.size == 0 ) }
+ rescue Errno::ENOENT
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
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"
+ return false if name[-3..] != ".rb"
end
# Check chefignore
- ignorer = parent
+ ignorer = self
+
loop do
- if ignorer.is_a?(CookbooksDir)
+ if ignorer.is_a?(ChefRepositoryFileSystemCookbookDir)
# Grab the path from entry to child
path_to_child = name
child = self
- while child.parent != ignorer
+ while child != ignorer
path_to_child = PathUtils.join(child.name, path_to_child)
child = child.parent
end
@@ -103,6 +102,7 @@ class Chef
if child.exists?
raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, child)
end
+
if file_contents
child.write(file_contents)
else
@@ -123,9 +123,10 @@ class Chef
FileSystemCache.instance.delete!(file_path)
begin
if dir?
- if !recurse
+ unless recurse
raise MustDeleteRecursivelyError.new(self, $!)
end
+
FileUtils.rm_r(file_path)
else
File.delete(file_path)
@@ -136,15 +137,13 @@ class Chef
end
def exists?
- File.exists?(file_path) && (parent.nil? || parent.can_have_child?(name, dir?))
+ File.exist?(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
+ File.open(file_path, "rb", &:read)
+ rescue Errno::ENOENT
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
end
def write(content)
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
index 1b26ced372..f7c00e7754 100644
--- 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
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,34 +16,35 @@
# limitations under the License.
#
-require "chef/chef_fs/file_system/base_fs_dir"
-require "chef/chef_fs/file_system/repository/acls_dir"
-require "chef/chef_fs/file_system/repository/clients_dir"
-require "chef/chef_fs/file_system/repository/cookbooks_dir"
-require "chef/chef_fs/file_system/repository/cookbook_artifacts_dir"
-require "chef/chef_fs/file_system/repository/containers_dir"
-require "chef/chef_fs/file_system/repository/data_bags_dir"
-require "chef/chef_fs/file_system/repository/environments_dir"
-require "chef/chef_fs/file_system/repository/groups_dir"
-require "chef/chef_fs/file_system/repository/nodes_dir"
-require "chef/chef_fs/file_system/repository/policy_groups_dir"
-require "chef/chef_fs/file_system/repository/roles_dir"
-require "chef/chef_fs/file_system/repository/users_dir"
-require "chef/chef_fs/file_system/repository/client_keys_dir"
-require "chef/chef_fs/file_system/repository/file_system_entry"
-require "chef/chef_fs/file_system/repository/policies_dir"
-require "chef/chef_fs/file_system/repository/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/client_key_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"
+require_relative "../base_fs_dir"
+require_relative "acls_dir"
+require_relative "clients_dir"
+require_relative "cookbooks_dir"
+require_relative "cookbook_artifacts_dir"
+require_relative "containers_dir"
+require_relative "data_bags_dir"
+require_relative "environments_dir"
+require_relative "groups_dir"
+require_relative "nodes_dir"
+require_relative "policy_groups_dir"
+require_relative "roles_dir"
+require_relative "users_dir"
+require_relative "client_keys_dir"
+require_relative "file_system_entry"
+require_relative "policies_dir"
+require_relative "versioned_cookbooks_dir"
+require_relative "../multiplexed_dir"
+require_relative "../../data_handler/client_data_handler"
+require_relative "../../data_handler/client_key_data_handler"
+require_relative "../../data_handler/environment_data_handler"
+require_relative "../../data_handler/node_data_handler"
+require_relative "../../data_handler/policy_data_handler"
+require_relative "../../data_handler/policy_group_data_handler"
+require_relative "../../data_handler/role_data_handler"
+require_relative "../../data_handler/user_data_handler"
+require_relative "../../data_handler/group_data_handler"
+require_relative "../../data_handler/container_data_handler"
+require_relative "../../../win32/security" if ChefUtils.windows?
class Chef
module ChefFS
@@ -83,19 +84,19 @@ class Chef
attr_reader :child_paths
attr_reader :versioned_cookbooks
- CHILDREN = %w{org.json invitations.json members.json}
+ CHILDREN = %w{org.json invitations.json members.json}.freeze
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 }
+ result.select { |c| c && c.exists? }.sort_by(&:name)
end
end
def can_have_child?(name, is_dir)
if is_dir
- child_paths.has_key?(name)
+ child_paths.key?(name)
elsif root_dir
CHILDREN.include?(name)
else
@@ -108,10 +109,23 @@ class Chef
child = root_dir.create_child(name, file_contents)
else
child_paths[name].each do |path|
- begin
- Dir.mkdir(path)
- rescue Errno::EEXIST
+
+ ::FileUtils.mkdir_p(path)
+ ::FileUtils.chmod(0700, path)
+ if ChefUtils.windows?
+ all_mask = Chef::ReservedNames::Win32::API::Security::GENERIC_ALL
+ administrators = Chef::ReservedNames::Win32::Security::SID.Administrators
+ owner = Chef::ReservedNames::Win32::Security::SID.default_security_object_owner
+ dacl = Chef::ReservedNames::Win32::Security::ACL.create([
+ Chef::ReservedNames::Win32::Security::ACE.access_allowed(owner, all_mask),
+ Chef::ReservedNames::Win32::Security::ACE.access_allowed(administrators, all_mask),
+ ])
+ so = Chef::ReservedNames::Win32::Security::SecurableObject.new(path)
+ so.owner = owner
+ so.set_dacl(dacl, false)
end
+ rescue Errno::EEXIST
+
end
child = make_child_entry(name)
end
@@ -126,15 +140,15 @@ class Chef
# 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"
+ result = "repository at #{repo_paths.join(", ")}"
if versioned_cookbooks
- result << " Multiple versions per cookbook\n"
+ result << " (Multiple versions per cookbook)"
else
- result << " One version per cookbook\n"
+ result << " (One version per cookbook)"
end
child_paths.each_pair do |name, paths|
if paths.any? { |path| !repo_paths.include?(File.dirname(path)) }
- result << " #{name} at #{paths.join(', ')}\n"
+ result << " #{name} at #{paths.join(", ")}\n"
end
end
result
@@ -147,7 +161,7 @@ class Chef
# members.json and org.json may be found.
#
def root_dir
- existing_paths = root_paths.select { |path| File.exists?(path) }
+ existing_paths = root_paths.select { |path| File.exist?(path) }
if existing_paths.size > 0
MultiplexedDir.new(existing_paths.map do |path|
dir = FileSystemEntry.new(name, parent, path)
@@ -165,14 +179,16 @@ class Chef
#
def make_child_entry(name)
if CHILDREN.include?(name)
- return nil if !root_dir
+ return nil unless root_dir
+
return root_dir.child(name)
end
- paths = (child_paths[name] || []).select { |path| File.exists?(path) }
+ paths = (child_paths[name] || []).select { |path| File.exist?(path) }
if paths.size == 0
return NonexistentFSObject.new(name, self)
end
+
case name
when "acls"
dirs = paths.map { |path| AclsDir.new(name, self, path) }
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
index 5dc74d85da..353f4536bb 100644
--- 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
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir"
+require_relative "chef_repository_file_system_cookbook_dir"
class Chef
module ChefFS
@@ -25,14 +25,15 @@ class Chef
class ChefRepositoryFileSystemVersionedCookbookDir < ChefRepositoryFileSystemCookbookDir
# Override from parent
def cookbook_version
- loader = Chef::Cookbook::CookbookVersionLoader.new(file_path, parent.chefignore)
+ loader = Chef::Cookbook::CookbookVersionLoader.new(file_path, 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.load!
loader.cookbook_version
end
end
diff --git a/lib/chef/chef_fs/file_system/repository/client.rb b/lib/chef/chef_fs/file_system/repository/client.rb
index 6a99b7f005..cc5bc9ec7a 100644
--- a/lib/chef/chef_fs/file_system/repository/client.rb
+++ b/lib/chef/chef_fs/file_system/repository/client.rb
@@ -1,6 +1,6 @@
#
# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/data_handler/client_data_handler"
-require "chef/chef_fs/file_system/repository/base_file"
+require_relative "../../data_handler/client_data_handler"
+require_relative "base_file"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/client_key.rb b/lib/chef/chef_fs/file_system/repository/client_key.rb
index 8ca4f85d2f..c6fa1399d3 100644
--- a/lib/chef/chef_fs/file_system/repository/client_key.rb
+++ b/lib/chef/chef_fs/file_system/repository/client_key.rb
@@ -1,6 +1,6 @@
#
# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/data_handler/client_key_data_handler"
-require "chef/chef_fs/file_system/repository/base_file"
+require_relative "../../data_handler/client_key_data_handler"
+require_relative "base_file"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/client_keys_dir.rb b/lib/chef/chef_fs/file_system/repository/client_keys_dir.rb
index 9e7e7b3d5c..28d2f9dc05 100644
--- a/lib/chef/chef_fs/file_system/repository/client_keys_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/client_keys_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: Jordan Running (<jr@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/chef_fs/file_system/repository/client_keys_sub_dir"
-require "chef/chef_fs/data_handler/client_key_data_handler"
-require "chef/chef_fs/file_system/repository/directory"
+require_relative "client_keys_sub_dir"
+require_relative "../../data_handler/client_key_data_handler"
+require_relative "directory"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/client_keys_sub_dir.rb b/lib/chef/chef_fs/file_system/repository/client_keys_sub_dir.rb
index 6aafcfe294..ff0a4602af 100644
--- a/lib/chef/chef_fs/file_system/repository/client_keys_sub_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/client_keys_sub_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: Jordan Running (<jr@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/chef_fs/file_system/repository/client_key"
-require "chef/chef_fs/data_handler/client_key_data_handler"
-require "chef/chef_fs/file_system/repository/directory"
+require_relative "client_key"
+require_relative "../../data_handler/client_key_data_handler"
+require_relative "directory"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/clients_dir.rb b/lib/chef/chef_fs/file_system/repository/clients_dir.rb
index 01027f83ac..c466a205c3 100644
--- a/lib/chef/chef_fs/file_system/repository/clients_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/clients_dir.rb
@@ -1,7 +1,7 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
# Author:: Ho-Sheng Hsiao (<hosh@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,9 +17,9 @@
# limitations under the License.
#
-require "chef/chef_fs/file_system/repository/client"
-require "chef/chef_fs/file_system/repository/directory"
-require "chef/chef_fs/file_system/exceptions"
+require_relative "client"
+require_relative "directory"
+require_relative "../exceptions"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/container.rb b/lib/chef/chef_fs/file_system/repository/container.rb
index e14a2ded73..1ea64643e0 100644
--- a/lib/chef/chef_fs/file_system/repository/container.rb
+++ b/lib/chef/chef_fs/file_system/repository/container.rb
@@ -1,6 +1,6 @@
#
# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/data_handler/container_data_handler"
-require "chef/chef_fs/file_system/repository/base_file"
+require_relative "../../data_handler/container_data_handler"
+require_relative "base_file"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/containers_dir.rb b/lib/chef/chef_fs/file_system/repository/containers_dir.rb
index 2af496f418..ca01b1df00 100644
--- a/lib/chef/chef_fs/file_system/repository/containers_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/containers_dir.rb
@@ -1,7 +1,7 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
# Author:: Ho-Sheng Hsiao (<hosh@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,9 +17,9 @@
# limitations under the License.
#
-require "chef/chef_fs/file_system/repository/container"
-require "chef/chef_fs/file_system/repository/directory"
-require "chef/chef_fs/file_system/exceptions"
+require_relative "container"
+require_relative "directory"
+require_relative "../exceptions"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/cookbook_artifacts_dir.rb b/lib/chef/chef_fs/file_system/repository/cookbook_artifacts_dir.rb
index 773d9a5e5a..bac9de3729 100644
--- a/lib/chef/chef_fs/file_system/repository/cookbook_artifacts_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/cookbook_artifacts_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/file_system/repository/cookbooks_dir"
-require "chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_artifact_dir"
+require_relative "cookbooks_dir"
+require_relative "chef_repository_file_system_cookbook_artifact_dir"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/cookbooks_dir.rb b/lib/chef/chef_fs/file_system/repository/cookbooks_dir.rb
index 0fd249a2c4..c0751e8fe2 100644
--- a/lib/chef/chef_fs/file_system/repository/cookbooks_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/cookbooks_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/chef_fs/file_system/repository/directory"
-require "chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir"
-require "chef/cookbook/chefignore"
+require_relative "directory"
+require_relative "chef_repository_file_system_cookbook_dir"
+require_relative "../../../cookbook/chefignore"
class Chef
module ChefFS
@@ -28,7 +28,7 @@ class Chef
class CookbooksDir < Repository::Directory
def chefignore
- @chefignore ||= Chef::Cookbook::Chefignore.new(self.file_path)
+ @chefignore ||= Chef::Cookbook::Chefignore.new(file_path)
rescue Errno::EISDIR, Errno::EACCES
# Work around a bug in Chefignore when chefignore is a directory
end
diff --git a/lib/chef/chef_fs/file_system/repository/data_bag.rb b/lib/chef/chef_fs/file_system/repository/data_bag.rb
index fb3ad9da77..759c09af8c 100644
--- a/lib/chef/chef_fs/file_system/repository/data_bag.rb
+++ b/lib/chef/chef_fs/file_system/repository/data_bag.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/file_system/repository/directory"
-require "chef/chef_fs/file_system/repository/data_bag_item"
+require_relative "directory"
+require_relative "data_bag_item"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/data_bag_item.rb b/lib/chef/chef_fs/file_system/repository/data_bag_item.rb
index 2e3fb39606..31a6777508 100644
--- a/lib/chef/chef_fs/file_system/repository/data_bag_item.rb
+++ b/lib/chef/chef_fs/file_system/repository/data_bag_item.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/data_handler/data_bag_item_data_handler"
-require "chef/chef_fs/file_system/repository/base_file"
+require_relative "../../data_handler/data_bag_item_data_handler"
+require_relative "base_file"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/data_bags_dir.rb b/lib/chef/chef_fs/file_system/repository/data_bags_dir.rb
index 666fede62b..0ce6cb753e 100644
--- a/lib/chef/chef_fs/file_system/repository/data_bags_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/data_bags_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/file_system/repository/directory"
-require "chef/chef_fs/file_system/repository/data_bag"
+require_relative "directory"
+require_relative "data_bag"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/directory.rb b/lib/chef/chef_fs/file_system/repository/directory.rb
index 328cf92b03..72a12d665f 100644
--- a/lib/chef/chef_fs/file_system/repository/directory.rb
+++ b/lib/chef/chef_fs/file_system/repository/directory.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/file_system_cache"
+require_relative "../../file_system_cache"
class Chef
module ChefFS
@@ -71,9 +71,10 @@ class Chef
def children
return FileSystemCache.instance.children(file_path) if FileSystemCache.instance.exist?(file_path)
- children = dir_ls.sort.
- map { |child_name| make_child_entry(child_name) }.
- select { |new_child| new_child.fs_entry_valid? && can_have_child?(new_child.name, new_child.dir?) }
+
+ children = dir_ls.sort
+ .map { |child_name| make_child_entry(child_name) }
+ .select { |new_child| new_child.fs_entry_valid? && can_have_child?(new_child.name, new_child.dir?) }
FileSystemCache.instance.set_children(file_path, children)
rescue Errno::ENOENT => e
raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
@@ -84,6 +85,7 @@ class Chef
if child.exists?
raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, child)
end
+
FileSystemCache.instance.delete!(child.file_path)
if file_contents
child.write(file_contents)
@@ -102,7 +104,7 @@ class Chef
children.empty?
end
- # Public API callied by chef_fs/file_system
+ # Public API called by chef_fs/file_system
def child(name)
possible_child = make_child_entry(name)
if possible_child.name_valid?
@@ -122,6 +124,7 @@ class Chef
if exists?
raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self)
end
+
begin
FileSystemCache.instance.delete!(file_path)
Dir.mkdir(file_path)
@@ -136,9 +139,10 @@ class Chef
def delete(recurse)
if exists?
- if !recurse
+ unless recurse
raise MustDeleteRecursivelyError.new(self, $!)
end
+
FileUtils.rm_r(file_path)
FileSystemCache.instance.delete!(file_path)
else
@@ -147,7 +151,7 @@ class Chef
end
def exists?
- File.exists?(file_path)
+ File.exist?(file_path)
end
protected
diff --git a/lib/chef/chef_fs/file_system/repository/environment.rb b/lib/chef/chef_fs/file_system/repository/environment.rb
index 9ef9741308..759807c869 100644
--- a/lib/chef/chef_fs/file_system/repository/environment.rb
+++ b/lib/chef/chef_fs/file_system/repository/environment.rb
@@ -1,6 +1,6 @@
#
# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/data_handler/environment_data_handler"
-require "chef/chef_fs/file_system/repository/base_file"
+require_relative "../../data_handler/environment_data_handler"
+require_relative "base_file"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/environments_dir.rb b/lib/chef/chef_fs/file_system/repository/environments_dir.rb
index 4d04348d6e..3c5c69fdfb 100644
--- a/lib/chef/chef_fs/file_system/repository/environments_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/environments_dir.rb
@@ -1,7 +1,7 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
# Author:: Ho-Sheng Hsiao (<hosh@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,9 +17,9 @@
# limitations under the License.
#
-require "chef/chef_fs/file_system/repository/environment"
-require "chef/chef_fs/data_handler/environment_data_handler"
-require "chef/chef_fs/file_system/repository/directory"
+require_relative "environment"
+require_relative "../../data_handler/environment_data_handler"
+require_relative "directory"
class Chef
module ChefFS
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
index 45ae002521..902bc63038 100644
--- a/lib/chef/chef_fs/file_system/repository/file_system_entry.rb
+++ b/lib/chef/chef_fs/file_system/repository/file_system_entry.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,11 +16,11 @@
# 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/exceptions"
-require "chef/chef_fs/path_utils"
-require "fileutils"
+require_relative "../base_fs_dir"
+require_relative "../chef_server/rest_list_dir"
+require_relative "../exceptions"
+require_relative "../../path_utils"
+require "fileutils" unless defined?(FileUtils)
class Chef
module ChefFS
@@ -80,9 +80,9 @@ class Chef
def children
# Except cookbooks and data bag dirs, all things must be json files
- Dir.entries(file_path).sort.
- map { |child_name| make_child_entry(child_name) }.
- select { |new_child| new_child.fs_entry_valid? && can_have_child?(new_child.name, new_child.dir?) }
+ Dir.entries(file_path).sort
+ .map { |child_name| make_child_entry(child_name) }
+ .select { |new_child| new_child.fs_entry_valid? && can_have_child?(new_child.name, new_child.dir?) }
rescue Errno::ENOENT
raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
end
@@ -92,6 +92,7 @@ class Chef
if child.exists?
raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, child)
end
+
if file_contents
child.write(file_contents)
else
@@ -108,9 +109,10 @@ class Chef
def delete(recurse)
if dir?
- if !recurse
+ unless recurse
raise MustDeleteRecursivelyError.new(self, $!)
end
+
FileUtils.rm_r(file_path)
else
File.delete(file_path)
@@ -120,11 +122,11 @@ class Chef
end
def exists?
- File.exists?(file_path) && (parent.nil? || parent.can_have_child?(name, dir?))
+ File.exist?(file_path) && (parent.nil? || parent.can_have_child?(name, dir?))
end
def read
- File.open(file_path, "rb") { |f| f.read }
+ File.open(file_path, "rb", &:read)
rescue Errno::ENOENT
raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
end
diff --git a/lib/chef/chef_fs/file_system/repository/group.rb b/lib/chef/chef_fs/file_system/repository/group.rb
index 302e72739b..42c54c5daa 100644
--- a/lib/chef/chef_fs/file_system/repository/group.rb
+++ b/lib/chef/chef_fs/file_system/repository/group.rb
@@ -1,6 +1,6 @@
#
# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/data_handler/group_data_handler"
-require "chef/chef_fs/file_system/repository/base_file"
+require_relative "../../data_handler/group_data_handler"
+require_relative "base_file"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/groups_dir.rb b/lib/chef/chef_fs/file_system/repository/groups_dir.rb
index 20728d1248..b0e9defa7b 100644
--- a/lib/chef/chef_fs/file_system/repository/groups_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/groups_dir.rb
@@ -1,7 +1,7 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
# Author:: Ho-Sheng Hsiao (<hosh@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,9 +17,9 @@
# limitations under the License.
#
-require "chef/chef_fs/file_system/repository/group"
-require "chef/chef_fs/file_system/repository/directory"
-require "chef/chef_fs/file_system/exceptions"
+require_relative "group"
+require_relative "directory"
+require_relative "../exceptions"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/node.rb b/lib/chef/chef_fs/file_system/repository/node.rb
index d8f0dec7c4..d7bbe9beef 100644
--- a/lib/chef/chef_fs/file_system/repository/node.rb
+++ b/lib/chef/chef_fs/file_system/repository/node.rb
@@ -1,6 +1,6 @@
#
# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/data_handler/node_data_handler"
-require "chef/chef_fs/file_system/repository/base_file"
+require_relative "../../data_handler/node_data_handler"
+require_relative "base_file"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/nodes_dir.rb b/lib/chef/chef_fs/file_system/repository/nodes_dir.rb
index 33ca7ca709..3c45df4863 100644
--- a/lib/chef/chef_fs/file_system/repository/nodes_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/nodes_dir.rb
@@ -1,7 +1,7 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
# Author:: Ho-Sheng Hsiao (<hosh@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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 "chef/chef_fs/file_system/repository/node"
-require "chef/chef_fs/file_system/repository/directory"
-require "chef/chef_fs/file_system/exceptions"
+require_relative "node"
+require_relative "directory"
+require_relative "../exceptions"
+require_relative "../../../win32/security" if ChefUtils.windows?
class Chef
module ChefFS
@@ -30,6 +31,27 @@ class Chef
def make_child_entry(child_name)
Node.new(child_name, self)
end
+
+ def create_child(child_name, file_contents = nil)
+ child = super
+ File.chmod(0600, child.file_path)
+ if ChefUtils.windows?
+ read_mask = Chef::ReservedNames::Win32::API::Security::GENERIC_READ
+ write_mask = Chef::ReservedNames::Win32::API::Security::GENERIC_WRITE
+ administrators = Chef::ReservedNames::Win32::Security::SID.Administrators
+ owner = Chef::ReservedNames::Win32::Security::SID.default_security_object_owner
+ dacl = Chef::ReservedNames::Win32::Security::ACL.create([
+ Chef::ReservedNames::Win32::Security::ACE.access_allowed(owner, read_mask),
+ Chef::ReservedNames::Win32::Security::ACE.access_allowed(owner, write_mask),
+ Chef::ReservedNames::Win32::Security::ACE.access_allowed(administrators, read_mask),
+ Chef::ReservedNames::Win32::Security::ACE.access_allowed(administrators, write_mask),
+ ])
+ so = Chef::ReservedNames::Win32::Security::SecurableObject.new(child.file_path)
+ so.owner = owner
+ so.set_dacl(dacl, false)
+ end
+ child
+ end
end
end
end
diff --git a/lib/chef/chef_fs/file_system/repository/policies_dir.rb b/lib/chef/chef_fs/file_system/repository/policies_dir.rb
index c74ea5469b..612bcbf3d8 100644
--- a/lib/chef/chef_fs/file_system/repository/policies_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/policies_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/chef_fs/file_system/repository/policy"
-require "chef/chef_fs/file_system/repository/directory"
-require "chef/chef_fs/data_handler/policy_data_handler"
+require_relative "policy"
+require_relative "directory"
+require_relative "../../data_handler/policy_data_handler"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/policy.rb b/lib/chef/chef_fs/file_system/repository/policy.rb
index 695bf17e83..67823445f8 100644
--- a/lib/chef/chef_fs/file_system/repository/policy.rb
+++ b/lib/chef/chef_fs/file_system/repository/policy.rb
@@ -1,6 +1,6 @@
#
# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/data_handler/policy_data_handler"
-require "chef/chef_fs/file_system/repository/base_file"
+require_relative "../../data_handler/policy_data_handler"
+require_relative "base_file"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/policy_group.rb b/lib/chef/chef_fs/file_system/repository/policy_group.rb
index e4182847b6..187ce21ef2 100644
--- a/lib/chef/chef_fs/file_system/repository/policy_group.rb
+++ b/lib/chef/chef_fs/file_system/repository/policy_group.rb
@@ -1,6 +1,6 @@
#
# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/data_handler/policy_group_data_handler"
-require "chef/chef_fs/file_system/repository/base_file"
+require_relative "../../data_handler/policy_group_data_handler"
+require_relative "base_file"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/policy_groups_dir.rb b/lib/chef/chef_fs/file_system/repository/policy_groups_dir.rb
index 4db85a93f7..c7388c8616 100644
--- a/lib/chef/chef_fs/file_system/repository/policy_groups_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/policy_groups_dir.rb
@@ -1,7 +1,7 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
# Author:: Ho-Sheng Hsiao (<hosh@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,9 +17,9 @@
# limitations under the License.
#
-require "chef/chef_fs/file_system/repository/policy_group"
-require "chef/chef_fs/file_system/repository/directory"
-require "chef/chef_fs/file_system/exceptions"
+require_relative "policy_group"
+require_relative "directory"
+require_relative "../exceptions"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/role.rb b/lib/chef/chef_fs/file_system/repository/role.rb
index d97ae0e7a1..512b917d20 100644
--- a/lib/chef/chef_fs/file_system/repository/role.rb
+++ b/lib/chef/chef_fs/file_system/repository/role.rb
@@ -1,6 +1,6 @@
#
# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/data_handler/role_data_handler"
-require "chef/chef_fs/file_system/repository/base_file"
+require_relative "../../data_handler/role_data_handler"
+require_relative "base_file"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/roles_dir.rb b/lib/chef/chef_fs/file_system/repository/roles_dir.rb
index 42f4376e71..86dbb2bafd 100644
--- a/lib/chef/chef_fs/file_system/repository/roles_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/roles_dir.rb
@@ -1,7 +1,7 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
# Author:: Ho-Sheng Hsiao (<hosh@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,9 +17,9 @@
# limitations under the License.
#
-require "chef/chef_fs/file_system/repository/role"
-require "chef/chef_fs/file_system/repository/directory"
-require "chef/chef_fs/file_system/exceptions"
+require_relative "role"
+require_relative "directory"
+require_relative "../exceptions"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/user.rb b/lib/chef/chef_fs/file_system/repository/user.rb
index 59355cc303..f3b8f1da38 100644
--- a/lib/chef/chef_fs/file_system/repository/user.rb
+++ b/lib/chef/chef_fs/file_system/repository/user.rb
@@ -1,6 +1,6 @@
#
# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/data_handler/user_data_handler"
-require "chef/chef_fs/file_system/repository/base_file"
+require_relative "../../data_handler/user_data_handler"
+require_relative "base_file"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/users_dir.rb b/lib/chef/chef_fs/file_system/repository/users_dir.rb
index 9e8621575b..36ef0ba80d 100644
--- a/lib/chef/chef_fs/file_system/repository/users_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/users_dir.rb
@@ -1,7 +1,7 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
# Author:: Ho-Sheng Hsiao (<hosh@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,9 +17,9 @@
# limitations under the License.
#
-require "chef/chef_fs/file_system/repository/user"
-require "chef/chef_fs/file_system/repository/directory"
-require "chef/chef_fs/file_system/exceptions"
+require_relative "user"
+require_relative "directory"
+require_relative "../exceptions"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/repository/versioned_cookbooks_dir.rb b/lib/chef/chef_fs/file_system/repository/versioned_cookbooks_dir.rb
index 80f77d02be..7989d14a2b 100644
--- a/lib/chef/chef_fs/file_system/repository/versioned_cookbooks_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/versioned_cookbooks_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs/file_system/repository/cookbooks_dir"
-require "chef/chef_fs/file_system/repository/chef_repository_file_system_versioned_cookbook_dir"
+require_relative "cookbooks_dir"
+require_relative "chef_repository_file_system_versioned_cookbook_dir"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system_cache.rb b/lib/chef/chef_fs/file_system_cache.rb
index a9d8d8bfe4..7944178f99 100644
--- a/lib/chef/chef_fs/file_system_cache.rb
+++ b/lib/chef/chef_fs/file_system_cache.rb
@@ -1,5 +1,5 @@
#
-# Copyright:: Copyright 2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,8 +15,8 @@
# limitations under the License.
#
-require "singleton"
-require "chef/client"
+require "singleton" unless defined?(Singleton)
+require_relative "../client"
class Chef
module ChefFS
@@ -51,7 +51,7 @@ class Chef
def delete!(path)
parent = _get_parent(path)
- Chef::Log.debug("Deleting parent #{parent} and #{path} from FileSystemCache")
+ Chef::Log.trace("Deleting parent #{parent} and #{path} from FileSystemCache")
if @cache.key?(path)
@cache.delete(path)
end
@@ -73,6 +73,7 @@ class Chef
def _get_parent(path)
parts = ChefFS::PathUtils.split(path)
return nil if parts.nil? || parts.length < 2
+
ChefFS::PathUtils.join(*parts[0..-2])
end
end
diff --git a/lib/chef/chef_fs/knife.rb b/lib/chef/chef_fs/knife.rb
index 1731e19ce1..ba993beee4 100644
--- a/lib/chef/chef_fs/knife.rb
+++ b/lib/chef/chef_fs/knife.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/knife"
-require "pathname"
+require_relative "../knife"
+require "pathname" unless defined?(Pathname)
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
module ChefFS
@@ -25,11 +26,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_relative "../config"
+ require_relative "parallelizer"
+ require_relative "config"
+ require_relative "file_pattern"
+ require_relative "path_utils"
yield
end
end
@@ -40,21 +41,19 @@ class Chef
# Ensure we always get to do our includes, whether subclass calls deps or not
c.deps do
end
-
- c.options.merge!(options)
end
option :repo_mode,
- :long => "--repo-mode MODE",
- :description => "Specifies the local repository layout. Values: static, everything, hosted_everything. Default: everything/hosted_everything"
+ 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 #{ChefUtils::Dist::Infra::PRODUCT} 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
diff --git a/lib/chef/chef_fs/parallelizer.rb b/lib/chef/chef_fs/parallelizer.rb
index ccbf7ad96e..c4d17a842d 100644
--- a/lib/chef/chef_fs/parallelizer.rb
+++ b/lib/chef/chef_fs/parallelizer.rb
@@ -1,5 +1,4 @@
-require "thread"
-require "chef/chef_fs/parallelizer/parallel_enumerable"
+require_relative "parallelizer/parallel_enumerable"
class Chef
module ChefFS
@@ -45,7 +44,7 @@ class Chef
end
def parallel_do(enumerable, options = {}, &block)
- ParallelEnumerable.new(@tasks, enumerable, options.merge(:ordered => false), &block).wait
+ ParallelEnumerable.new(@tasks, enumerable, options.merge(ordered: false), &block).wait
end
def stop(wait = true, timeout = nil)
@@ -86,19 +85,17 @@ class Chef
private
def worker_loop
- begin
- until @stop_thread[Thread.current]
- begin
- task = @tasks.pop
- task.call
- rescue
- puts "ERROR #{$!}"
- puts $!.backtrace
- end
+ until @stop_thread[Thread.current]
+ begin
+ task = @tasks.pop
+ task.call
+ rescue
+ puts "ERROR #{$!}"
+ puts $!.backtrace
end
- ensure
- @stop_thread.delete(Thread.current)
end
+ ensure
+ @stop_thread.delete(Thread.current)
end
end
end
diff --git a/lib/chef/chef_fs/parallelizer/parallel_enumerable.rb b/lib/chef/chef_fs/parallelizer/parallel_enumerable.rb
index ab578bdb7f..5fafc5c88f 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_relative "flatten_enumerable"
class Chef
module ChefFS
@@ -145,8 +145,9 @@ class Chef
def each_with_exceptions_unordered
if @each_running
- raise "each() called on parallel enumerable twice simultaneously! Bad mojo"
+ raise "each() called on parallel enumerable twice simultaneously! Bad mojo"
end
+
@each_running = true
begin
# Grab all the inputs, yielding any responses during enumeration
@@ -197,7 +198,7 @@ class Chef
# If we exited early, perhaps due to any? finding a result, we want
# to make sure and throw away any extra results (gracefully) so that
# the next enumerator can start over.
- if !finished?
+ unless finished?
stop
end
raise
@@ -234,7 +235,7 @@ class Chef
# The order of these checks is important, as well, to be thread safe.
# 1. If @unconsumed_input.empty? is true, then we will never have any more
# work legitimately picked up.
- # 2. If @in_process == 0, then there is no work in process, and because ofwhen unconsumed_input is empty, it will never go back up, because
+ # 2. If @in_process == 0, then there is no work in process, and because of when unconsumed_input is empty, it will never go back up, because
# this is called after the input enumerator is finished. Note that switching #2 and #1
# could cause a race, because in_process is incremented *before* consuming input.
# 3. If @unconsumed_output.empty? is true, then we are done with outputs.
diff --git a/lib/chef/chef_fs/path_utils.rb b/lib/chef/chef_fs/path_utils.rb
index 7b2de5e3e0..1682120c86 100644
--- a/lib/chef/chef_fs/path_utils.rb
+++ b/lib/chef/chef_fs/path_utils.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/chef_fs"
-require "pathname"
+require_relative "../chef_fs"
+require "pathname" unless defined?(Pathname)
class Chef
module ChefFS
@@ -42,6 +42,7 @@ class Chef
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)
@@ -57,7 +58,7 @@ class Chef
end
def self.regexp_path_separator
- Chef::ChefFS.windows? ? '[\/\\\\]' : "/"
+ ChefUtils.windows? ? '[\/\\\\]' : "/"
end
# Given a server path, determines if it is absolute.
@@ -70,9 +71,9 @@ class Chef
# 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'
+ # If /x is a symlink to /foo_bar, and has no subdirectories, then:
+ # PathUtils.realest_path('/x/y/z') == '/foo_bar/y/z'
+ # PathUtils.realest_path('/x/*/z') == '/foo_bar/*/z'
# PathUtils.realest_path('/*/y/z') == '/*/y/z'
#
# TODO: Move this to wherever util/path_helper is these days.
@@ -87,10 +88,11 @@ class Chef
# 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
+ rescue Errno::ENOENT, Errno::EINVAL
suffix << File.basename(path)
path = parent_path
parent_path = File.dirname(path)
@@ -99,9 +101,9 @@ class Chef
File.join(path, *suffix.reverse)
end
- # Compares two path fragments according to the case-sentitivity of the host platform.
+ # Compares two path fragments according to the case-sensitivity of the host platform.
def self.os_path_eq?(left, right)
- Chef::ChefFS.windows? ? left.casecmp(right) == 0 : left == right
+ ChefUtils.windows? ? left.casecmp(right) == 0 : left == right
end
# Given two general OS-dependent file paths, determines the relative path of the
@@ -113,9 +115,10 @@ class Chef
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}/
+ elsif /#{PathUtils.regexp_path_separator}/.match?(path[ancestor.length, 1])
path[ancestor.length + 1..-1]
else
nil
diff --git a/lib/chef/client.rb b/lib/chef/client.rb
index c857da1b93..094b59fc35 100644
--- a/lib/chef/client.rb
+++ b/lib/chef/client.rb
@@ -3,7 +3,7 @@
# Author:: Christopher Walters (<cw@chef.io>)
# Author:: Christopher Brown (<cb@chef.io>)
# Author:: Tim Hinderliter (<tim@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,52 +18,55 @@
# 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/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/data_collector"
-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"
+require_relative "config"
+require_relative "mixin/params_validate"
+require "chef-utils/dsl/default_paths" unless defined?(ChefUtils::DSL::DefaultPaths)
+require_relative "log"
+require_relative "deprecated"
+require_relative "server_api"
+require_relative "api_client"
+require_relative "api_client/registration"
+require_relative "node"
+require_relative "role"
+require_relative "file_cache"
+Chef.autoload :RunContext, File.expand_path("run_context", __dir__)
+require_relative "runner"
+require_relative "run_status"
+require_relative "cookbook/cookbook_collection"
+require_relative "cookbook/file_vendor"
+require_relative "cookbook/file_system_file_vendor"
+require_relative "cookbook/remote_file_vendor"
+require_relative "event_dispatch/dispatcher"
+require_relative "event_loggers/base"
+require_relative "event_loggers/windows_eventlog"
+require_relative "exceptions"
+require_relative "formatters/base"
+require_relative "formatters/doc"
+require_relative "formatters/minimal"
+require_relative "version"
+require_relative "action_collection"
+require_relative "resource_reporter"
+require_relative "data_collector"
+require_relative "run_lock"
+Chef.autoload :PolicyBuilder, File.expand_path("policy_builder", __dir__)
+require_relative "request_id"
+require_relative "platform/rebooter"
+require_relative "mixin/deprecation"
+require "chef-utils" unless defined?(ChefUtils::CANARY)
+require "ohai" unless defined?(Ohai::System)
+require "rbconfig" unless defined?(RbConfig)
+require "forwardable" unless defined?(Forwardable)
+
+require_relative "compliance/runner"
class Chef
# == Chef::Client
# The main object in a Chef run. Preps a Chef::Node and Chef::RunContext,
# syncs cookbooks if necessary, and triggers convergence.
class Client
- include Chef::Mixin::PathSanity
-
extend Chef::Mixin::Deprecation
+ extend Forwardable
#
# The status of the Chef run.
#
@@ -72,6 +75,13 @@ class Chef
attr_reader :run_status
#
+ # The run context of the Chef run.
+ #
+ # @return [Chef::RunContext]
+ #
+ attr_reader :run_context
+
+ #
# The node represented by this client.
#
# @return [Chef::Node]
@@ -92,21 +102,6 @@ class Chef
attr_reader :ohai
#
- # The rest object used to communicate with the Chef server.
- #
- # @return [Chef::ServerAPI]
- #
- attr_reader :rest
-
- #
- # A rest object with validate_utf8 set to false. This will not throw exceptions
- # on non-UTF8 strings in JSON but will sanitize them so that e.g. POSTs will
- # never fail. Cannot be configured on a request-by-request basis, so we carry
- # around another rest object for it.
- #
- attr_reader :rest_clean
-
- #
# The runner used to converge.
#
# @return [Chef::Runner]
@@ -142,6 +137,10 @@ class Chef
#
attr_reader :events
+ attr_reader :logger
+
+ def_delegator :@run_context, :transport_connection
+
#
# Creates a new Chef::Client.
#
@@ -155,13 +154,15 @@ class Chef
#
def initialize(json_attribs = nil, args = {})
@json_attribs = json_attribs || {}
- @ohai = Ohai::System.new
+ @logger = args.delete(:logger) || Chef::Log.with_child
+
+ @ohai = Ohai::System.new(logger: logger)
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.
+ # @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)
@@ -222,30 +223,11 @@ class Chef
# @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
@@ -253,106 +235,113 @@ class Chef
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("Platform: #{RUBY_PLATFORM}")
- Chef::Log.info "Chef-client pid: #{Process.pid}"
- Chef::Log.debug("Chef-client request_id: #{request_id}")
- enforce_path_sanity
+ events.register(Chef::DataCollector::Reporter.new(events))
+ events.register(Chef::ActionCollection.new(events))
+ events.register(Chef::Compliance::Runner.new)
+
+ run_status.run_id = request_id = Chef::RequestID.instance.request_id
+
+ @run_context = Chef::RunContext.new
+ run_context.events = events
+ run_status.run_context = run_context
+
+ events.run_start(Chef::VERSION, run_status)
+ logger.info("*** #{ChefUtils::Dist::Infra::PRODUCT} #{Chef::VERSION} ***")
+ logger.info("Platform: #{RUBY_PLATFORM}")
+ logger.info "#{ChefUtils::Dist::Infra::CLIENT.capitalize} pid: #{Process.pid}"
+ logger.info "Targeting node: #{Chef::Config.target_mode.host}" if Chef::Config.target_mode?
+ logger.debug("#{ChefUtils::Dist::Infra::CLIENT.capitalize} request_id: #{request_id}")
+ logger.warn("`enforce_path_sanity` is deprecated, please use `enforce_default_paths` instead!") if Chef::Config[:enforce_path_sanity]
+ ENV["PATH"] = ChefUtils::DSL::DefaultPaths.default_paths if Chef::Config[:enforce_default_paths] || Chef::Config[:enforce_path_sanity]
+
run_ohai
- register unless Chef::Config[:solo_legacy_mode]
- register_data_collector_reporter
+ unless Chef::Config[:solo_legacy_mode]
+ register
+
+ # create and save the rest objects in the run_context
+ run_context.rest = rest
+ run_context.rest_clean = rest_clean
+
+ events.register(Chef::ResourceReporter.new(rest_clean))
+ end
load_node
build_node
- run_status.run_id = request_id
run_status.start_clock
- Chef::Log.info("Starting Chef Run for #{node.name}")
+ logger.info("Starting #{ChefUtils::Dist::Infra::PRODUCT} Run for #{node.name}")
run_started
do_windows_admin_check
- run_context = setup_run_context
+ Chef.resource_handler_map.lock!
+ Chef.provider_handler_map.lock!
- if Chef::Config[:audit_mode] != :audit_only
- converge_error = converge_and_save(run_context)
- end
+ setup_run_context
- 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
+ load_required_recipe(@rest, run_context) unless Chef::Config[:solo_legacy_mode]
- # Raise converge_error so run_failed reporters/events are processed.
- raise converge_error if converge_error
+ converge_and_save(run_context)
run_status.stop_clock
- Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds")
+ logger.info("#{ChefUtils::Dist::Infra::PRODUCT} Run complete in #{run_status.elapsed_time} seconds")
run_completed_successfully
- events.run_completed(node)
+ events.run_completed(node, run_status)
# keep this inside the main loop to get exception backtraces
end_profiling
+ warn_if_eol
+
# 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 ")}")
+ logger.trace("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)
+ events.run_failed(run_error, run_status)
+ Chef::Application.debug_stacktrace(run_error)
+ raise run_error
ensure
Chef::RequestID.instance.reset_request_id
@run_status = 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
+ # @todo make this stuff protected or private
#
# @api private
+ def warn_if_eol
+ require_relative "version"
+
+ # We make a release every year so take the version you're on + 2006 and you get
+ # the year it goes EOL
+ eol_year = 2006 + Gem::Version.new(Chef::VERSION).segments.first
+
+ if Time.now > Time.new(eol_year, 5, 01)
+ logger.warn("This release of #{ChefUtils::Dist::Infra::PRODUCT} became end of life (EOL) on May 1st #{eol_year}. Please update to a supported release to receive new features, bug fixes, and security updates.")
+ end
+ end
+
+ # @api private
def configure_formatters
formatters_for_run.map do |formatter_name, output_path|
if output_path.nil?
Chef::Formatters.new(formatter_name, STDOUT_FD, STDERR_FD)
- else
+ elsif output_path.is_a?(String)
io = File.open(output_path, "a+")
io.sync = true
Chef::Formatters.new(formatter_name, io, io)
@@ -362,19 +351,15 @@ class Chef
# @api private
def formatters_for_run
- if Chef::Config.formatters.empty?
- [default_formatter]
- else
- Chef::Config.formatters
- end
- end
+ return Chef::Config.formatters unless Chef::Config.formatters.empty?
- # @api private
- def default_formatter
- if (STDOUT.tty? && !Chef::Config[:force_logger]) || Chef::Config[:force_formatter]
- [:doc]
- else
- [:null]
+ [ Chef::Config[:log_location] ].flatten.map do |log_location|
+ log_location = nil if log_location == STDOUT
+ if !Chef::Config[:force_logger] || Chef::Config[:force_formatter]
+ [:doc, log_location]
+ else
+ [:null]
+ end
end
end
@@ -395,26 +380,27 @@ class Chef
end
end
- # Rest client for use by API reporters. This rest client will not fail with an exception if
- # it is fed non-UTF8 data.
+ # Standard rest object for talking to the Chef Server
+ #
+ # FIXME: Can we drop this and only use the rest_clean object? Did I add rest_clean
+ # only out of some cant-break-a-minor-version paranoia?
#
# @api private
- def rest_clean(client_name = node_name, config = Chef::Config)
- @rest_clean ||=
- Chef::ServerAPI.new(config[:chef_server_url], client_name: client_name,
- signing_key_filename: config[:client_key], validate_utf8: false)
+ def rest
+ @rest ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], client_name: node_name,
+ signing_key_filename: Chef::Config[:client_key])
end
- # Resource reporters send event information back to the chef server for
- # processing. Can only be called after we have a @rest object
+ # A rest object with validate_utf8 set to false. This will not throw exceptions
+ # on non-UTF8 strings in JSON but will sanitize them so that e.g. POSTs will
+ # never fail. Cannot be configured on a request-by-request basis, so we carry
+ # around another rest object for it.
+ #
# @api private
- def register_reporters
- [
- Chef::ResourceReporter.new(rest_clean),
- Chef::Audit::AuditReporter.new(rest_clean),
- ].each do |r|
- events.register(r)
- end
+ def rest_clean
+ @rest_clean ||=
+ Chef::ServerAPI.new(Chef::Config[:chef_server_url], client_name: node_name,
+ signing_key_filename: Chef::Config[:client_key], validate_utf8: false)
end
#
@@ -507,10 +493,53 @@ class Chef
#
# @api private
def setup_run_context
- run_context = policy_builder.setup_run_context(specific_recipes)
+ @run_context = policy_builder.setup_run_context(specific_recipes, run_context)
assert_cookbook_path_not_empty(run_context)
- run_status.run_context = run_context
+ run_status.run_context = run_context # backcompat for chefspec
+ run_context
+ end
+
+ #
+ # Adds a required recipe as specified by the Chef Server
+ #
+ # @return The modified run context
+ #
+ # @api private
+ #
+ # TODO: @rest doesn't appear to be used anywhere outside
+ # of client.register except for here. If it's common practice
+ # to create your own rest client, perhaps we should do that
+ # here but it seems more appropriate to reuse one that we
+ # know is already created. for ease of testing, we'll pass
+ # the existing rest client in as a parameter
+ #
+ def load_required_recipe(rest, run_context)
+ required_recipe_contents = rest.get("required_recipe")
+ logger.info("Required Recipe found, loading it")
+ Chef::FileCache.store("required_recipe", required_recipe_contents)
+ required_recipe_file = Chef::FileCache.load("required_recipe", false)
+
+ # TODO: add integration tests with resource reporting turned on
+ # (presumably requires changes to chef-zero)
+ #
+ # Chef::Recipe.new takes a cookbook name and a recipe name along
+ # with the run context. These names are eventually used in the
+ # resource reporter, and if the cookbook name cannot be found in the
+ # cookbook collection then we will fail with an exception. Cases where
+ # we currently also fail:
+ # - specific recipes
+ # - chef-apply would fail if resource reporting was enabled
+ #
+ recipe = Chef::Recipe.new(nil, nil, run_context)
+ recipe.from_file(required_recipe_file)
run_context
+ rescue Net::HTTPClientException => e
+ case e.response
+ when Net::HTTPNotFound
+ logger.trace("Required Recipe not configured on the server, skipping it")
+ else
+ raise
+ end
end
#
@@ -538,9 +567,9 @@ class Chef
if Chef::Config[:solo_legacy_mode]
# nothing to do
elsif policy_builder.temporary_policy?
- Chef::Log.warn("Skipping final node save because override_runlist was given")
+ logger.warn("Skipping final node save because override_runlist was given")
else
- Chef::Log.debug("Saving the current state of node #{node_name}")
+ logger.debug("Saving the current state of node #{node_name}")
node.save
end
end
@@ -556,7 +585,8 @@ class Chef
# @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 ohai_time os os_version init_package} : nil
+ ohai.transport_connection = transport_connection if Chef::Config.target_mode?
ohai.all_plugins(filter)
events.ohai_completed(node)
end
@@ -610,22 +640,16 @@ class Chef
def register(client_name = node_name, config = Chef::Config)
if !config[:client_key]
events.skipping_registration(client_name, config)
- Chef::Log.debug("Client key is unspecified - skipping registration")
+ logger.trace("Client key is unspecified - skipping registration")
elsif File.exists?(config[:client_key])
events.skipping_registration(client_name, config)
- Chef::Log.debug("Client key #{config[:client_key]} is present - skipping registration")
+ logger.trace("Client key #{config[:client_key]} is present - skipping registration")
else
events.registration_start(node_name, config)
- Chef::Log.info("Client key #{config[:client_key]} is not present - registering")
+ logger.info("Client key #{config[:client_key]} is not present - registering")
Chef::ApiClient::Registration.new(node_name, config[:client_key]).run
events.registration_completed
end
- # We now have the client key, and should use it from now on.
- @rest = Chef::ServerAPI.new(config[:chef_server_url], client_name: client_name,
- signing_key_filename: config[:client_key])
- # force initialization of the rest_clean API object
- rest_clean(client_name, config)
- register_reporters
rescue Exception => e
# TODO this should probably only ever fire if we *started* registration.
# Move it to the block above.
@@ -645,14 +669,9 @@ class Chef
#
# @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.
+ # @raise Any converge 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
@@ -660,93 +679,32 @@ class Chef
# @api private
#
def converge(run_context)
- converge_exception = nil
catch(:end_client_run_early) do
- begin
- events.converge_start(run_context)
- Chef::Log.debug("Converging node #{node_name}")
- @runner = Chef::Runner.new(run_context)
- @runner.converge
- events.converge_complete
- rescue Exception => e
- events.converge_failed(e)
- raise e if Chef::Config[:audit_mode] == :disabled
- converge_exception = e
- end
+
+ events.converge_start(run_context)
+ logger.debug("Converging node #{node_name}")
+ @runner = Chef::Runner.new(run_context)
+ @runner.converge
+ events.converge_complete
+ rescue Exception => e
+ events.converge_failed(e)
+ raise e
+
end
- converge_exception
end
- #
# Converge the node via and then save it if successful.
#
- # @param run_context The run context.
- #
- # @return The thrown exception, if we are in audit mode. `nil` means the
- # converge was successful or ended early.
- #
- # @raise Any converge or node save exception, unless we are in audit mode,
- # in which case we *return* the exception.
+ # If converge() raises it is important that save_updated_node is bypassed.
#
- # @see #converge
- # @see #save_updated_mode
- # @see Chef::Config#audit_mode
+ # @param run_context [Chef::RunContext] The run context.
+ # @raise Any converge or node save exception
#
# @api private
#
- # We don't want to change the old API on the `converge` method to have it perform
- # saving. So we wrap it in this method.
- # TODO given this seems to be pretty internal stuff, how badly do we need to
- # split this stuff up?
- #
def converge_and_save(run_context)
- converge_exception = converge(run_context)
- unless converge_exception
- begin
- save_updated_node
- rescue Exception => e
- raise e if Chef::Config[:audit_mode] == :disabled
- converge_exception = e
- end
- end
- converge_exception
- end
-
- #
- # Run the audit phase.
- #
- # Triggers the audit_phase_start, audit_phase_complete and
- # audit_phase_failed events.
- #
- # @param run_context The run context.
- #
- # @return Any thrown exceptions. `nil` if successful.
- #
- # @see Chef::Audit::Runner#run
- # @see Chef::EventDispatch#audit_phase_start
- # @see Chef::EventDispatch#audit_phase_complete
- # @see Chef::EventDispatch#audit_phase_failed
- #
- # @api private
- #
- def run_audits(run_context)
- begin
- events.audit_phase_start(run_status)
- Chef::Log.info("Starting audit phase")
- auditor = Chef::Audit::Runner.new(run_context)
- auditor.run
- if auditor.failed?
- 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
- rescue Exception => e
- Chef::Log.error("Audit phase failed with error message: #{e.message}")
- @events.audit_phase_failed(e, Chef::Audit::Logger.read_buffer)
- audit_exception = e
- end
- audit_exception
+ converge(run_context)
+ save_updated_node
end
#
@@ -774,20 +732,20 @@ class Chef
# @api private
#
def do_windows_admin_check
- if Chef::Platform.windows?
- Chef::Log.debug("Checking for administrator privileges....")
+ if ChefUtils.windows?
+ logger.trace("Checking for administrator privileges....")
if !has_admin_privileges?
- message = "chef-client doesn't have administrator privileges on node #{node_name}."
+ message = "#{ChefUtils::Dist::Infra::CLIENT} doesn't have administrator privileges on node #{node_name}."
if Chef::Config[:fatal_windows_admin_check]
- Chef::Log.fatal(message)
- Chef::Log.fatal("fatal_windows_admin_check is set to TRUE.")
+ logger.fatal(message)
+ logger.fatal("fatal_windows_admin_check is set to TRUE.")
raise Chef::Exceptions::WindowsNotAdmin, message
else
- Chef::Log.warn("#{message} This might cause unexpected resource failures.")
+ logger.warn("#{message} This might cause unexpected resource failures.")
end
else
- Chef::Log.debug("chef-client has administrator privileges on node #{node_name}.")
+ logger.trace("#{ChefUtils::Dist::Infra::CLIENT} has administrator privileges on node #{node_name}.")
end
end
end
@@ -893,15 +851,6 @@ class Chef
#
STDERR_FD = STDERR
- #
- # Deprecated writers
- #
-
- include Chef::Mixin::Deprecation
- deprecated_attr_writer :ohai, "There is no alternative. Leave ohai alone!"
- deprecated_attr_writer :rest, "There is no alternative. Leave rest alone!"
- deprecated_attr :runner, "There is no alternative. Leave runner alone!"
-
private
attr_reader :override_runlist
@@ -915,18 +864,20 @@ class Chef
def start_profiling
return unless Chef::Config[:profile_ruby]
+
profiling_prereqs!
RubyProf.start
end
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}")
+ logger.warn("Ruby execution profile dumped to #{path}")
end
def empty_directory?(path)
@@ -934,7 +885,7 @@ class Chef
end
def is_last_element?(index, object)
- object.kind_of?(Array) ? index == object.size - 1 : true
+ object.is_a?(Array) ? index == object.size - 1 : true
end
def assert_cookbook_path_not_empty(run_context)
@@ -943,32 +894,26 @@ class Chef
# Chef::Config[:cookbook_path] can be a string or an array
# if it's an array, go through it and check each one, raise error at the last one if no files are found
cookbook_paths = Array(Chef::Config[:cookbook_path])
- Chef::Log.debug "Loading from cookbook_path: #{cookbook_paths.map { |path| File.expand_path(path) }.join(', ')}"
+ logger.trace "Loading from cookbook_path: #{cookbook_paths.map { |path| File.expand_path(path) }.join(", ")}"
if cookbook_paths.all? { |path| empty_directory?(path) }
msg = "None of the cookbook paths set in Chef::Config[:cookbook_path], #{cookbook_paths.inspect}, contain any cookbooks"
- Chef::Log.fatal(msg)
+ logger.fatal(msg)
raise Chef::Exceptions::CookbookNotFound, msg
end
else
- Chef::Log.warn("Node #{node_name} has an empty run list.") if run_context.node.run_list.empty?
+ logger.warn("Node #{node_name} has an empty run list.") if run_context.node.run_list.empty?
end
end
def has_admin_privileges?
- require "chef/win32/security"
+ require_relative "win32/security"
Chef::ReservedNames::Win32::Security.has_admin_privileges?
end
-
- # Register the data collector reporter to send event information to the
- # data collector server
- def register_data_collector_reporter
- events.register(Chef::DataCollector::Reporter.new) if Chef::DataCollector.register_reporter?
- 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_relative "cookbook_loader"
+require_relative "cookbook_version"
+require_relative "cookbook/synchronizer"
diff --git a/lib/chef/compliance/default_attributes.rb b/lib/chef/compliance/default_attributes.rb
new file mode 100644
index 0000000000..9b368d4f64
--- /dev/null
+++ b/lib/chef/compliance/default_attributes.rb
@@ -0,0 +1,93 @@
+# Author:: Stephan Renatus <srenatus@chef.io>
+# Copyright:: (c) 2016-2019, Chef Software Inc. <legal@chef.io>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR 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/attribute_collections" # for VividMash
+require "chef/util/path_helper"
+
+class Chef
+ module Compliance
+ DEFAULT_ATTRIBUTES = Chef::Node::VividMash.new(
+ # If enabled, a cache is built for all backend calls. This should only be
+ # disabled if you are expecting unique results from the same backend call.
+ # Under the covers, this controls :command and :file caching on Chef InSpec's
+ # Train connection.
+ "inspec_backend_cache" => true,
+
+ # Controls what is done with the resulting report after the Chef InSpec run.
+ # Accepts a single string value or an array of multiple values.
+ # Accepted values: 'chef-server-automate', 'chef-automate', 'json-file', 'audit-enforcer'
+ "reporter" => "json-file",
+
+ # Controls if Chef InSpec profiles should be fetched from Chef Automate or Chef Infra Server
+ # in addition to the default fetch locations provided by Chef Inspec.
+ # Accepted values: nil, 'chef-server', 'chef-automate'
+ "fetcher" => nil,
+
+ # Allow for connections to HTTPS endpoints using self-signed ssl certificates.
+ "insecure" => nil,
+
+ # Controls verbosity of Chef InSpec runner.
+ "quiet" => true,
+
+ # Chef Inspec Compliance profiles to be used for scan of node.
+ # See README.md for details
+ "profiles" => {},
+
+ # Extra inputs passed to Chef InSpec to allow finer-grained control over behavior.
+ # These are mapped to Chef InSpec's inputs, but are named attributes here for legacy reasons.
+ # See Chef Inspec's documentation for more information: https://docs.chef.io/inspec/inputs/
+ "attributes" => {},
+
+ # A string path or an array of paths to Chef InSpec waiver files.
+ # See Chef Inspec's documentation for more information: https://docs.chef.io/inspec/waivers/
+ "waiver_file" => nil,
+
+ "json_file" => {
+ # The location on disk that Chef InSpec's json reports are saved to when using the
+ # 'json-file' reporter. Defaults to:
+ # <chef_cache_path>/compliance_reports/compliance-<timestamp>.json
+ "location" => Chef::Util::PathHelper.join(
+ Chef::Config[:cache_path],
+ "compliance_reports",
+ Time.now.utc.strftime("compliance-%Y%m%d%H%M%S.json")
+ ),
+ },
+
+ # Control results that have a `run_time` below this limit will
+ # be stripped of the `start_time` and `run_time` fields to
+ # reduce the size of the reports being sent to Chef Automate.
+ "run_time_limit" => 1.0,
+
+ # A control result message that exceeds this character limit will be truncated.
+ # This helps keep reports to a reasonable size. On rare occasions, we've seen messages exceeding 9 MB in size,
+ # causing the report to not be ingested in the backend because of the 4 MB report size rpc limitation.
+ # Chef InSpec will append this text at the end of any truncated messages: `[Truncated to 10000 characters]`
+ "result_message_limit" => 10000,
+
+ # When a Chef InSpec resource throws an exception, results will contain a short error message and a
+ # detailed ruby stacktrace of the error. This attribute instructs Chef InSpec not to include the detailed stacktrace in order
+ # to keep the overall report to a manageable size.
+ "result_include_backtrace" => false,
+
+ # The array of results per control will be truncated at this limit to avoid large reports that cannot be
+ # processed by Chef Automate. A summary of removed results will be sent with each impacted control.
+ "control_results_limit" => 50,
+
+ # If enabled, a hash representation of the Chef Infra node object will be sent to Chef InSpec in an input
+ # named `chef_node`.
+ "chef_node_attribute_enabled" => false
+ )
+ end
+end
diff --git a/lib/chef/compliance/fetcher/automate.rb b/lib/chef/compliance/fetcher/automate.rb
new file mode 100644
index 0000000000..b254684280
--- /dev/null
+++ b/lib/chef/compliance/fetcher/automate.rb
@@ -0,0 +1,69 @@
+require "uri" unless defined?(URI)
+require "plugins/inspec-compliance/lib/inspec-compliance"
+
+class Chef
+ module Compliance
+ module Fetcher
+ class Automate < ::InspecPlugins::Compliance::Fetcher
+ name "chef-automate"
+
+ # Positions this fetcher before Chef InSpec's `compliance` fetcher.
+ # Only load this file if you want to use Compliance Phase in Chef Solo with Chef Automate.
+ priority 502
+
+ CONFIG = {
+ "insecure" => true,
+ "token" => nil,
+ "server_type" => "automate",
+ "automate" => {
+ "ent" => "default",
+ "token_type" => "dctoken",
+ },
+ }.freeze
+
+ def self.resolve(target)
+ uri = get_target_uri(target)
+ return nil if uri.nil?
+
+ config = CONFIG.dup
+
+ # we have detailed information available in our lockfile, no need to ask the server
+ if target.respond_to?(:key?) && target.key?(:url)
+ profile_fetch_url = target[:url]
+ else
+ # verifies that the target e.g base/ssh exists
+ base_path = "/compliance/profiles/#{uri.host}#{uri.path}"
+
+ profile_path = if target.respond_to?(:key?) && target.key?(:version)
+ "#{base_path}/version/#{target[:version]}/tar"
+ else
+ "#{base_path}/tar"
+ end
+
+ url = URI(Chef::Config[:data_collector][:server_url])
+ url.path = profile_path
+ profile_fetch_url = url.to_s
+
+ config["token"] = Chef::Config[:data_collector][:token]
+
+ if config["token"].nil?
+ raise Inspec::FetcherFailure,
+ "No data-collector token set, which is required by the chef-automate fetcher. " \
+ "Set the `data_collector.token` configuration parameter in your client.rb " \
+ 'or use the "chef-server-automate" reporter which does not require any ' \
+ "data-collector settings and uses #{ChefUtils::Dist::Server::PRODUCT} to fetch profiles."
+ end
+ end
+
+ new(profile_fetch_url, config)
+ rescue URI::Error => _e
+ nil
+ end
+
+ def to_s
+ "#{ChefUtils::Dist::Automate::PRODUCT} for #{ChefUtils::Dist::Solo::PRODUCT} Fetcher"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/compliance/fetcher/chef_server.rb b/lib/chef/compliance/fetcher/chef_server.rb
new file mode 100644
index 0000000000..96a2213b69
--- /dev/null
+++ b/lib/chef/compliance/fetcher/chef_server.rb
@@ -0,0 +1,134 @@
+require "uri" unless defined?(URI)
+require "plugins/inspec-compliance/lib/inspec-compliance"
+
+# This class implements an InSpec fetcher for Chef Server. The implementation
+# is based on the Chef Compliance fetcher and only adapts the calls to redirect
+# the requests via Chef Server.
+#
+# This implementation depends on chef-client runtime, therefore it is only executable
+# inside of a chef-client run
+
+class Chef
+ module Compliance
+ module Fetcher
+ class ChefServer < ::InspecPlugins::Compliance::Fetcher
+ name "chef-server"
+
+ # it positions itself before `compliance` fetcher
+ # only load it, if the Chef Server is integrated with Chef Compliance
+ priority 501
+
+ CONFIG = { "insecure" => true }.freeze
+
+ # Accepts URLs to compliance profiles in one of two forms:
+ # * a String URL with a compliance scheme, like "compliance://namespace/profile_name"
+ # * a Hash with a key of `compliance` and a value like "compliance/profile_name" and optionally a `version` key with a String value
+ def self.resolve(target)
+ profile_uri = get_target_uri(target)
+ return nil if profile_uri.nil?
+
+ organization = Chef::Config[:chef_server_url].split("/").last
+ owner = profile_uri.user ? "#{profile_uri.user}@#{profile_uri.host}" : profile_uri.host
+ version = target[:version] if target.respond_to?(:key?)
+
+ path_parts = [""]
+ path_parts << "compliance" if chef_server_reporter? || chef_server_fetcher?
+ path_parts << "organizations"
+ path_parts << organization
+ path_parts << "owners"
+ path_parts << owner
+ path_parts << "compliance"
+ path_parts << profile_uri.path
+ path_parts << "version/#{version}" if version
+ path_parts << "tar"
+
+ target_url = URI(Chef::Config[:chef_server_url])
+ target_url.path = File.join(path_parts)
+ Chef::Log.info("Fetching profile from: #{target_url}")
+
+ new(target_url, CONFIG)
+ rescue URI::Error => _e
+ nil
+ end
+
+ #
+ # We want to save compliance: in the lockfile rather than url: to
+ # make sure we go back through the ComplianceAPI handling.
+ #
+ def resolved_source
+ { compliance: chef_server_url }
+ end
+
+ # Downloads archive to temporary file using a Chef::ServerAPI
+ # client so that Chef Server's header-based authentication can be
+ # used.
+ def download_archive_to_temp
+ return @temp_archive_path unless @temp_archive_path.nil?
+
+ rest = Chef::ServerAPI.new(@target, Chef::Config.merge(ssl_verify_mode: :verify_none))
+ archive = with_http_rescue do
+ rest.streaming_request(@target)
+ end
+ @archive_type = ".tar.gz"
+
+ if archive.nil?
+ path = @target.respond_to?(:path) ? @target.path : path
+ raise Inspec::FetcherFailure, "Unable to find requested profile on path: '#{path}' on the #{ChefUtils::Dist::Automate::PRODUCT} system."
+ end
+
+ Inspec::Log.debug("Archive stored at temporary location: #{archive.path}")
+ @temp_archive_path = archive.path
+ end
+
+ def with_http_rescue
+ response = yield
+ if response.respond_to?(:code)
+ # handle non 200 error codes, they are not raised as Net::HTTPClientException
+ handle_http_error_code(response.code) if response.code.to_i >= 300
+ end
+ response
+ rescue Net::HTTPClientException => e
+ Chef::Log.error e
+ handle_http_error_code(e.response.code)
+ end
+
+ def handle_http_error_code(code)
+ case code
+ when /401|403/
+ Chef::Log.error "Auth issue: see the Compliance Phase troubleshooting documentation (http://docs.chef.io/chef_compliance_phase/#troubleshooting)."
+ when /404/
+ Chef::Log.error "Object does not exist on remote server."
+ when /413/
+ Chef::Log.error "You most likely hit the erchef request size in #{ChefUtils::Dist::Server::PRODUCT} that defaults to ~2MB. To increase this limit see the Compliance Phase troubleshooting documentation (http://docs.chef.io/chef_compliance_phase/#troubleshooting) or the Chef Infra Server configuration documentation (https://docs.chef.io/server/config_rb_server/)"
+ when /429/
+ Chef::Log.error "This error typically means the data sent was larger than #{ChefUtils::Dist::Automate::PRODUCT}'s limit (4 MB). Run InSpec locally to identify any controls producing large diffs."
+ end
+ msg = "Received HTTP error #{code}"
+ Chef::Log.error msg
+ raise Inspec::FetcherFailure, msg
+ end
+
+ def to_s
+ "#{ChefUtils::Dist::Server::PRODUCT}/Compliance Profile Loader"
+ end
+
+ CHEF_SERVER_REPORTERS = %w{chef-server chef-server-compliance chef-server-visibility chef-server-automate}.freeze
+ def self.chef_server_reporter?
+ (Array(Chef.node.attributes["audit"]["reporter"]) & CHEF_SERVER_REPORTERS).any?
+ end
+
+ CHEF_SERVER_FETCHERS = %w{chef-server chef-server-compliance chef-server-visibility chef-server-automate}.freeze
+ def self.chef_server_fetcher?
+ CHEF_SERVER_FETCHERS.include?(Chef.node.attributes["audit"]["fetcher"])
+ end
+
+ private
+
+ def chef_server_url
+ m = %r{^#{@config['server']}/owners/(?<owner>[^/]+)/compliance/(?<id>[^/]+)/tar$}.match(@target)
+ "#{m[:owner]}/#{m[:id]}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/compliance/reporter/automate.rb b/lib/chef/compliance/reporter/automate.rb
new file mode 100644
index 0000000000..cae0256085
--- /dev/null
+++ b/lib/chef/compliance/reporter/automate.rb
@@ -0,0 +1,201 @@
+class Chef
+ module Compliance
+ module Reporter
+ #
+ # Used to send inspec reports to Chef Automate via the data_collector service
+ #
+ class Automate
+ def initialize(opts)
+ @entity_uuid = opts[:entity_uuid]
+ @run_id = opts[:run_id]
+ @node_name = opts[:node_info][:node]
+ @environment = opts[:node_info][:environment]
+ @roles = opts[:node_info][:roles]
+ @recipes = opts[:node_info][:recipes]
+ @insecure = opts[:insecure]
+ @chef_tags = opts[:node_info][:chef_tags]
+ @policy_group = opts[:node_info][:policy_group]
+ @policy_name = opts[:node_info][:policy_name]
+ @source_fqdn = opts[:node_info][:source_fqdn]
+ @organization_name = opts[:node_info][:organization_name]
+ @ipaddress = opts[:node_info][:ipaddress]
+ @fqdn = opts[:node_info][:fqdn]
+ @run_time_limit = opts[:run_time_limit]
+ @control_results_limit = opts[:control_results_limit]
+ @timestamp = opts.fetch(:timestamp) { Time.now }
+
+ @url = Chef::Config[:data_collector][:server_url]
+ @token = Chef::Config[:data_collector][:token]
+ end
+
+ # Method used in order to send the inspec report to the data_collector server
+ def send_report(report)
+ unless @entity_uuid && @run_id
+ Chef::Log.error "entity_uuid(#{@entity_uuid}) or run_id(#{@run_id}) can't be nil, not sending report to #{ChefUtils::Dist::Automate::PRODUCT}"
+ return false
+ end
+
+ unless @url && @token
+ Chef::Log.warn "data_collector.token and data_collector.server_url must be defined in client.rb! Further information: https://docs.chef.io/chef_compliance_phase/#direct-reporting-to-chef-automate"
+ return false
+ end
+
+ headers = {
+ "Content-Type" => "application/json",
+ "x-data-collector-auth" => "version=1.0",
+ "x-data-collector-token" => @token,
+ }
+
+ all_report_shas = report[:profiles].map { |p| p[:sha256] }
+ missing_report_shas = missing_automate_profiles(headers, all_report_shas)
+
+ full_report = truncate_controls_results(enriched_report(report), @control_results_limit)
+ full_report = strip_profiles_meta(full_report, missing_report_shas, @run_time_limit)
+ json_report = Chef::JSONCompat.to_json(full_report, validate_utf8: false)
+
+ # Automate GRPC currently has a message limit of ~4MB
+ # https://github.com/chef/automate/issues/1417#issuecomment-541908157
+ if json_report.bytesize > 4 * 1024 * 1024
+ Chef::Log.warn "Generated report size is #{(json_report.bytesize / (1024 * 1024.0)).round(2)} MB. #{ChefUtils::Dist::Automate::PRODUCT} has an internal 4MB limit that is not currently configurable."
+ end
+
+ unless json_report
+ Chef::Log.warn "Something went wrong, report can't be nil"
+ return false
+ end
+
+ begin
+ Chef::Log.info "Report to #{ChefUtils::Dist::Automate::PRODUCT}: #{@url}"
+ Chef::Log.debug "Compliance Report: #{json_report}"
+ http_client.post(nil, json_report, headers)
+ true
+ rescue => e
+ Chef::Log.error "send_report: POST to #{@url} returned: #{e.message}"
+ false
+ end
+ end
+
+ def http_client(url = @url)
+ if @insecure
+ Chef::HTTP.new(url, ssl_verify_mode: :verify_none)
+ else
+ Chef::HTTP.new(url)
+ end
+ end
+
+ def enriched_report(final_report)
+ final_report[:profiles].compact!
+
+ # Label this content as an inspec_report
+ final_report[:type] = "inspec_report"
+
+ final_report[:node_name] = @node_name
+ final_report[:end_time] = @timestamp.utc.strftime("%FT%TZ")
+ final_report[:node_uuid] = @entity_uuid
+ final_report[:environment] = @environment
+ final_report[:roles] = @roles
+ final_report[:recipes] = @recipes
+ final_report[:report_uuid] = @run_id
+ final_report[:source_fqdn] = @source_fqdn
+ final_report[:organization_name] = @organization_name
+ final_report[:policy_group] = @policy_group
+ final_report[:policy_name] = @policy_name
+ final_report[:chef_tags] = @chef_tags
+ final_report[:ipaddress] = @ipaddress
+ final_report[:fqdn] = @fqdn
+
+ final_report
+ end
+
+ CONTROL_RESULT_SORT_ORDER = %w{ failed skipped passed }.freeze
+
+ # Truncates the number of results per control in the report when they exceed max_results.
+ # The truncation prioritizes failed and skipped results over passed ones.
+ # Controls where results have been truncated will get a new object 'removed_results_counts'
+ # with the status counts of the truncated results
+ def truncate_controls_results(report, max_results)
+ return report unless max_results.is_a?(Integer) && max_results > 0
+
+ report.fetch(:profiles, []).each do |profile|
+ profile.fetch(:controls, []).each do |control|
+ # Only bother with truncation if the number of results exceed max_results
+ next unless control[:results].length > max_results
+
+ res = control[:results]
+ res.sort_by! { |r| CONTROL_RESULT_SORT_ORDER.index(r[:status]) }
+
+ # Count the results that will be truncated
+ truncated = { failed: 0, skipped: 0, passed: 0 }
+ (max_results..res.length - 1).each do |i|
+ case res[i][:status]
+ when "failed"
+ truncated[:failed] += 1
+ when "skipped"
+ truncated[:skipped] += 1
+ when "passed"
+ truncated[:passed] += 1
+ end
+ end
+ # Truncate the results array now
+ control[:results] = res[0..max_results - 1]
+ control[:removed_results_counts] = truncated
+ end
+ end
+ report
+ end
+
+ # Contacts the metasearch Automate API to check which of the inspec profile sha256 ids
+ # passed in via `report_shas` are missing from the Automate profiles metadata database.
+ def missing_automate_profiles(headers, report_shas)
+ Chef::Log.debug "Checking the #{ChefUtils::Dist::Automate::PRODUCT} profiles metadata for: #{report_shas}"
+ meta_url = URI(@url)
+ meta_url.path = "/compliance/profiles/metasearch"
+ response_str = http_client(meta_url.to_s).post(nil, "{\"sha256\": #{report_shas}}", headers)
+ missing_shas = Chef::JSONCompat.parse(response_str)["missing_sha256"]
+ unless missing_shas.empty?
+ Chef::Log.info "#{ChefUtils::Dist::Automate::PRODUCT} is missing metadata for the following profile ids: #{missing_shas}"
+ end
+ missing_shas
+ rescue => e
+ Chef::Log.error "missing_automate_profiles error: #{e.message}"
+ # If we get an error it's safer to assume none of the profile shas exist in Automate
+ report_shas
+ end
+
+ # Profile 'name' is a required property.
+ # By not sending it in the report, we make it clear to the ingestion backend that the profile metadata has been stripped from this profile in the report.
+ # Profile 'title' and 'version' are still kept for troubleshooting purposes in the backend.
+ SEEN_PROFILE_UNNECESSARY_FIELDS = %i{ copyright copyright_email groups license maintainer name summary supports}.freeze
+
+ SEEN_PROFILE_UNNECESSARY_CONTROL_FIELDS = %i{ code desc descriptions impact refs source_location tags title }.freeze
+
+ # TODO: This mutates the report and probably doesn't need to.
+ def strip_profiles_meta(report, missing_report_shas, run_time_limit)
+ report[:profiles].each do |p|
+ next if missing_report_shas.include?(p[:sha256])
+
+ p.delete_if { |f| SEEN_PROFILE_UNNECESSARY_FIELDS.include?(f) }
+
+ next unless p[:controls].is_a?(Array)
+
+ p[:controls].each do |c|
+ c.delete_if { |f| SEEN_PROFILE_UNNECESSARY_CONTROL_FIELDS.include?(f) }
+ c.delete(:waiver_data) if c[:waiver_data] == {}
+
+ next unless c[:results].is_a?(Array)
+
+ c[:results].each do |r|
+ if r[:run_time].is_a?(Float) && r[:run_time] < run_time_limit
+ r.delete(:start_time)
+ r.delete(:run_time)
+ end
+ end
+ end
+ end
+ report[:run_time_limit] = run_time_limit
+ report
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/compliance/reporter/chef_server_automate.rb b/lib/chef/compliance/reporter/chef_server_automate.rb
new file mode 100644
index 0000000000..46ca7dc7ba
--- /dev/null
+++ b/lib/chef/compliance/reporter/chef_server_automate.rb
@@ -0,0 +1,94 @@
+require_relative "automate"
+
+class Chef
+ module Compliance
+ module Reporter
+ #
+ # Used to send inspec reports to Chef Automate server via Chef Server
+ #
+ class ChefServerAutomate < Chef::Compliance::Reporter::Automate
+ attr_reader :url
+
+ def initialize(opts)
+ @entity_uuid = opts[:entity_uuid]
+ @run_id = opts[:run_id]
+ @node_name = opts[:node_info][:node]
+ @insecure = opts[:insecure]
+ @environment = opts[:node_info][:environment]
+ @roles = opts[:node_info][:roles]
+ @recipes = opts[:node_info][:recipes]
+ @url = opts[:url]
+ @chef_tags = opts[:node_info][:chef_tags]
+ @policy_group = opts[:node_info][:policy_group]
+ @policy_name = opts[:node_info][:policy_name]
+ @source_fqdn = opts[:node_info][:source_fqdn]
+ @organization_name = opts[:node_info][:organization_name]
+ @ipaddress = opts[:node_info][:ipaddress]
+ @fqdn = opts[:node_info][:fqdn]
+ @control_results_limit = opts[:control_results_limit]
+ @timestamp = opts.fetch(:timestamp) { Time.now }
+ end
+
+ def send_report(report)
+ unless @entity_uuid && @run_id
+ Chef::Log.error "entity_uuid(#{@entity_uuid}) or run_id(#{@run_id}) can't be nil, not sending report to #{ChefUtils::Dist::Automate::PRODUCT}"
+ return false
+ end
+
+ automate_report = truncate_controls_results(enriched_report(report), @control_results_limit)
+
+ report_size = Chef::JSONCompat.to_json(automate_report, validate_utf8: false).bytesize
+ # this is set to slightly less than the oc_erchef limit
+ if report_size > 900 * 1024
+ Chef::Log.warn "Generated report size is #{(report_size / (1024 * 1024.0)).round(2)} MB. #{ChefUtils::Dist::Server::PRODUCT} < 13.0 defaults to a limit of ~1MB, 13.0+ defaults to a limit of ~2MB."
+ end
+
+ Chef::Log.info "Report to #{ChefUtils::Dist::Automate::PRODUCT} via #{ChefUtils::Dist::Server::PRODUCT}: #{@url}"
+ with_http_rescue do
+ http_client.post(@url, automate_report)
+ return true
+ end
+ false
+ end
+
+ def http_client
+ config = if @insecure
+ Chef::Config.merge(ssl_verify_mode: :verify_none)
+ else
+ Chef::Config
+ end
+
+ Chef::ServerAPI.new(@url, config)
+ end
+
+ def with_http_rescue
+ response = yield
+ if response.respond_to?(:code)
+ # handle non 200 error codes, they are not raised as Net::HTTPClientException
+ handle_http_error_code(response.code) if response.code.to_i >= 300
+ end
+ response
+ rescue Net::HTTPClientException => e
+ Chef::Log.error e
+ handle_http_error_code(e.response.code)
+ end
+
+ def handle_http_error_code(code)
+ case code
+ when /401|403/
+ Chef::Log.error "Auth issue: see the Compliance Phase troubleshooting documentation (http://docs.chef.io/chef_compliance_phase/#troubleshooting)."
+ when /404/
+ Chef::Log.error "Object does not exist on remote server."
+ when /413/
+ Chef::Log.error "You most likely hit the erchef request size in #{ChefUtils::Dist::Server::PRODUCT} that defaults to ~2MB. To increase this limit see the Compliance Phase troubleshooting documentation (http://docs.chef.io/chef_compliance_phase/#troubleshooting) or the Chef Infra Server configuration documentation (https://docs.chef.io/server/config_rb_server/)"
+ when /429/
+ Chef::Log.error "This error typically means the data sent was larger than #{ChefUtils::Dist::Automate::PRODUCT}'s limit (4 MB). Run InSpec locally to identify any controls producing large diffs."
+ end
+ msg = "Received HTTP error #{code}"
+ Chef::Log.error msg
+ raise msg
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/compliance/reporter/compliance_enforcer.rb b/lib/chef/compliance/reporter/compliance_enforcer.rb
new file mode 100644
index 0000000000..1c63e43b28
--- /dev/null
+++ b/lib/chef/compliance/reporter/compliance_enforcer.rb
@@ -0,0 +1,20 @@
+class Chef
+ module Compliance
+ module Reporter
+ class AuditEnforcer
+ class ControlFailure < StandardError; end
+
+ def send_report(report)
+ report.fetch(:profiles, []).each do |profile|
+ profile.fetch(:controls, []).each do |control|
+ control.fetch(:results, []).each do |result|
+ raise ControlFailure, "Audit #{control[:id]} has failed. Aborting #{ChefUtils::Dist::Infra::CLIENT} run." if result[:status] == "failed"
+ end
+ end
+ end
+ true
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/compliance/reporter/json_file.rb b/lib/chef/compliance/reporter/json_file.rb
new file mode 100644
index 0000000000..471d9f64b1
--- /dev/null
+++ b/lib/chef/compliance/reporter/json_file.rb
@@ -0,0 +1,19 @@
+require_relative "../../json_compat"
+
+class Chef
+ module Compliance
+ module Reporter
+ class JsonFile
+ def initialize(opts)
+ @path = opts.fetch(:file)
+ end
+
+ def send_report(report)
+ FileUtils.mkdir_p(File.dirname(@path), mode: 0700)
+
+ File.write(@path, Chef::JSONCompat.to_json(report))
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/compliance/runner.rb b/lib/chef/compliance/runner.rb
new file mode 100644
index 0000000000..86344367c2
--- /dev/null
+++ b/lib/chef/compliance/runner.rb
@@ -0,0 +1,266 @@
+autoload :Inspec, "inspec"
+
+require_relative "default_attributes"
+require_relative "reporter/automate"
+require_relative "reporter/chef_server_automate"
+require_relative "reporter/compliance_enforcer"
+require_relative "reporter/json_file"
+
+class Chef
+ module Compliance
+ class Runner < EventDispatch::Base
+ extend Forwardable
+
+ attr_accessor :run_id, :recipes
+ attr_reader :node
+ def_delegators :node, :logger
+
+ def enabled?
+ audit_cookbook_present = recipes.include?("audit::default")
+
+ logger.info("#{self.class}##{__method__}: #{Inspec::Dist::PRODUCT_NAME} profiles? #{inspec_profiles.any?}")
+ logger.info("#{self.class}##{__method__}: audit cookbook? #{audit_cookbook_present}")
+
+ inspec_profiles.any? && !audit_cookbook_present
+ end
+
+ def node=(node)
+ @node = node
+ node.default["audit"] = Chef::Compliance::DEFAULT_ATTRIBUTES.merge(node.default["audit"])
+ end
+
+ def node_load_completed(node, _expanded_run_list, _config)
+ self.node = node
+ end
+
+ def run_started(run_status)
+ self.run_id = run_status.run_id
+ end
+
+ def run_list_expanded(run_list_expansion)
+ self.recipes = run_list_expansion.recipes
+ end
+
+ def run_completed(_node, _run_status)
+ return unless enabled?
+
+ logger.info("#{self.class}##{__method__}: enabling Compliance Phase")
+
+ report
+ end
+
+ def run_failed(_exception, _run_status)
+ return unless enabled?
+
+ logger.info("#{self.class}##{__method__}: enabling Compliance Phase")
+
+ report
+ end
+
+ ### Below code adapted from audit cookbook's files/default/handler/audit_report.rb
+
+ DEPRECATED_CONFIG_VALUES = %w{
+ attributes_save
+ fail_if_not_present
+ inspec_gem_source
+ inspec_version
+ interval
+ owner
+ raise_if_unreachable
+ }.freeze
+
+ def warn_for_deprecated_config_values!
+ deprecated_config_values = (node["audit"].keys & DEPRECATED_CONFIG_VALUES)
+
+ if deprecated_config_values.any?
+ values = deprecated_config_values.sort.map { |v| "'#{v}'" }.join(", ")
+ logger.warn "audit cookbook config values #{values} are not supported in #{ChefUtils::Dist::Infra::PRODUCT}'s Compliance Phase."
+ end
+ end
+
+ def report(report = generate_report)
+ warn_for_deprecated_config_values!
+
+ if report.empty?
+ logger.error "Compliance report was not generated properly, skipped reporting"
+ return
+ end
+
+ Array(node["audit"]["reporter"]).each do |reporter|
+ send_report(reporter, report)
+ end
+ end
+
+ def inspec_opts
+ inputs = node["audit"]["attributes"].to_h
+ if node["audit"]["chef_node_attribute_enabled"]
+ inputs["chef_node"] = node.to_h
+ inputs["chef_node"]["chef_environment"] = node.chef_environment
+ end
+
+ {
+ backend_cache: node["audit"]["inspec_backend_cache"],
+ inputs: inputs,
+ logger: logger,
+ output: node["audit"]["quiet"] ? ::File::NULL : STDOUT,
+ report: true,
+ reporter: ["json-automate"],
+ reporter_backtrace_inclusion: node["audit"]["result_include_backtrace"],
+ reporter_message_truncation: node["audit"]["result_message_limit"],
+ waiver_file: Array(node["audit"]["waiver_file"]),
+ }
+ end
+
+ def inspec_profiles
+ profiles = node["audit"]["profiles"]
+
+ # TODO: Custom exception class here?
+ unless profiles.respond_to?(:map) && profiles.all? { |_, p| p.respond_to?(:transform_keys) && p.respond_to?(:update) }
+ raise "#{Inspec::Dist::PRODUCT_NAME} profiles specified in an unrecognized format, expected a hash of hashes."
+ end
+
+ profiles.map do |name, profile|
+ profile.transform_keys(&:to_sym).update(name: name)
+ end
+ end
+
+ def load_fetchers!
+ case node["audit"]["fetcher"]
+ when "chef-automate"
+ require_relative "fetcher/automate"
+ when "chef-server"
+ require_relative "fetcher/chef_server"
+ when nil
+ # intentionally blank
+ else
+ raise "Invalid value specified for Compliance Phase's fetcher: '#{node["audit"]["fetcher"]}'. Valid values are 'chef-automate', 'chef-server', or nil."
+ end
+ end
+
+ def generate_report(opts: inspec_opts, profiles: inspec_profiles)
+ load_fetchers!
+
+ logger.debug "Options are set to: #{opts}"
+ runner = ::Inspec::Runner.new(opts)
+
+ if profiles.empty?
+ failed_report("No #{Inspec::Dist::PRODUCT_NAME} profiles are defined.")
+ return
+ end
+
+ profiles.each { |target| runner.add_target(target) }
+
+ logger.info "Running profiles from: #{profiles.inspect}"
+ runner.run
+ runner.report.tap do |r|
+ logger.debug "Compliance Report #{r}"
+ end
+ rescue Inspec::FetcherFailure => e
+ failed_report("Cannot fetch all profiles: #{profiles}. Please make sure you're authenticated and the server is reachable. #{e.message}")
+ rescue => e
+ failed_report(e.message)
+ end
+
+ # In case InSpec raises a runtime exception without providing a valid report,
+ # we make one up and add two new fields to it: `status` and `status_message`
+ def failed_report(err)
+ logger.error "#{Inspec::Dist::PRODUCT_NAME} has raised a runtime exception. Generating a minimal failed report."
+ logger.error err
+ {
+ "platform": {
+ "name": "unknown",
+ "release": "unknown",
+ },
+ "profiles": [],
+ "statistics": {
+ "duration": 0.0000001,
+ },
+ "version": Inspec::VERSION,
+ "status": "failed",
+ "status_message": err,
+ }
+ end
+
+ # extracts relevant node data
+ def node_info
+ chef_server_uri = URI(Chef::Config[:chef_server_url])
+
+ runlist_roles = node.run_list.select { |item| item.type == :role }.map(&:name)
+ runlist_recipes = node.run_list.select { |item| item.type == :recipe }.map(&:name)
+ {
+ node: node.name,
+ os: {
+ release: node["platform_version"],
+ family: node["platform"],
+ },
+ environment: node.environment,
+ roles: runlist_roles,
+ recipes: runlist_recipes,
+ policy_name: node.policy_name || "",
+ policy_group: node.policy_group || "",
+ chef_tags: node.tags,
+ organization_name: chef_server_uri.path.split("/").last || "",
+ source_fqdn: chef_server_uri.host || "",
+ ipaddress: node["ipaddress"],
+ fqdn: node["fqdn"],
+ }
+ end
+
+ def send_report(reporter_type, report)
+ logger.info "Reporting to #{reporter_type}"
+
+ reporter = reporter(reporter_type)
+
+ reporter.send_report(report) if reporter
+ end
+
+ def reporter(reporter_type)
+ case reporter_type
+ when "chef-automate"
+ opts = {
+ control_results_limit: node["audit"]["control_results_limit"],
+ entity_uuid: node["chef_guid"],
+ insecure: node["audit"]["insecure"],
+ node_info: node_info,
+ run_id: run_id,
+ run_time_limit: node["audit"]["run_time_limit"],
+ }
+ Chef::Compliance::Reporter::Automate.new(opts)
+ when "chef-server-automate"
+ opts = {
+ control_results_limit: node["audit"]["control_results_limit"],
+ entity_uuid: node["chef_guid"],
+ insecure: node["audit"]["insecure"],
+ node_info: node_info,
+ run_id: run_id,
+ run_time_limit: node["audit"]["run_time_limit"],
+ url: chef_server_automate_url,
+ }
+ Chef::Compliance::Reporter::ChefServerAutomate.new(opts)
+ when "json-file"
+ path = node["audit"]["json_file"]["location"]
+ logger.info "Writing compliance report to #{path}"
+ Chef::Compliance::Reporter::JsonFile.new(file: path)
+ when "audit-enforcer"
+ Chef::Compliance::Reporter::ComplianceEnforcer.new
+ else
+ raise "'#{reporter_type}' is not a supported reporter for Compliance Phase."
+ end
+ end
+
+ def chef_server_automate_url
+ url = if node["audit"]["server"]
+ URI(node["audit"]["server"])
+ else
+ URI(Chef::Config[:chef_server_url]).tap do |u|
+ u.path = ""
+ end
+ end
+
+ org = Chef::Config[:chef_server_url].split("/").last
+ url.path = File.join(url.path, "organizations/#{org}/data-collector")
+ url
+ end
+ end
+ end
+end
diff --git a/lib/chef/config.rb b/lib/chef/config.rb
index 549872bfd7..f92aae87c0 100644
--- a/lib/chef/config.rb
+++ b/lib/chef/config.rb
@@ -4,7 +4,7 @@
# Author:: AJ Christensen (<aj@chef.io>)
# Author:: Mark Mzyk (<mmzyk@chef.io>)
# Author:: Kyle Goodwin (<kgoodwin@primerevenue.com>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,7 +19,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require "chef/log"
+require_relative "log"
require "chef-config/logger"
# DI our logger into ChefConfig before we load the config. Some defaults are
@@ -28,7 +28,8 @@ require "chef-config/logger"
ChefConfig.logger = Chef::Log
require "chef-config/config"
-require "chef/platform/query_helpers"
+require "chef-utils" unless defined?(ChefUtils::CANARY)
+require_relative "platform/query_helpers"
# Ohai::Config defines its own log_level and log_location. When loaded, it will
# override the default ChefConfig::Config values. We save them here before
@@ -48,15 +49,14 @@ class Chef
# 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:
+ # We make exceptions 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 :event_loggers do
evt_loggers = []
- if ChefConfig.windows? && !(Chef::Platform.windows_server_2003? ||
- Chef::Platform.windows_nano_server?)
+ if ChefUtils.windows?
evt_loggers << :win_evt
end
evt_loggers
@@ -75,7 +75,7 @@ class Chef
# by redefining the config_attr_writer to not warn for these options.
#
# REMOVEME once the warnings for these configurables are removed from Ohai.
- [ :log_level, :log_location ].each do |option|
+ %i{log_level log_location}.each do |option|
config_attr_writer option do |value|
value
end
diff --git a/lib/chef/config_fetcher.rb b/lib/chef/config_fetcher.rb
index ee1b64956a..0e4a7935ac 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_relative "application"
+require_relative "chef_fs/path_utils"
+require_relative "http/simple"
+require_relative "json_compat"
class Chef
class ConfigFetcher
@@ -25,7 +25,7 @@ class Chef
begin
Chef::JSONCompat.from_json(config_data)
rescue Chef::Exceptions::JSON::ParseError => error
- Chef::Application.fatal!("Could not parse the provided JSON file (#{config_location}): " + error.message, Chef::Exceptions::DeprecatedExitCode.new)
+ Chef::Application.fatal!("Could not parse the provided JSON file (#{config_location}): " + error.message)
end
end
@@ -39,16 +39,16 @@ class Chef
def fetch_remote_config
http.get("")
- rescue SocketError, SystemCallError, Net::HTTPServerException => error
- Chef::Application.fatal!("Cannot fetch config '#{config_location}': '#{error.class}: #{error.message}", Chef::Exceptions::DeprecatedExitCode.new)
+ rescue SocketError, SystemCallError, Net::HTTPClientException => error
+ Chef::Application.fatal!("Cannot fetch config '#{config_location}': '#{error.class}: #{error.message}")
end
def read_local_config
::File.read(config_location)
rescue Errno::ENOENT
- Chef::Application.fatal!("Cannot load configuration from #{config_location}", Chef::Exceptions::DeprecatedExitCode.new)
+ Chef::Application.fatal!("Cannot load configuration from #{config_location}")
rescue Errno::EACCES
- Chef::Application.fatal!("Permissions are incorrect on #{config_location}. Please chmod a+r #{config_location}", Chef::Exceptions::DeprecatedExitCode.new)
+ Chef::Application.fatal!("Permissions are incorrect on #{config_location}. Please chmod a+r #{config_location}")
end
def config_missing?
@@ -58,7 +58,7 @@ class Chef
Pathname.new(config_location).realpath.to_s
false
rescue Errno::ENOENT
- return true
+ true
end
def http
diff --git a/lib/chef/constants.rb b/lib/chef/constants.rb
index f32c3e6654..0c78c9ee19 100644
--- a/lib/chef/constants.rb
+++ b/lib/chef/constants.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser <jkeiser@chef.io>
-# Copyright:: Copyright 2015-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/lib/chef/cookbook/chefignore.rb b/lib/chef/cookbook/chefignore.rb
index 71ef53c9e5..41a0e44c54 100644
--- a/lib/chef/cookbook/chefignore.rb
+++ b/lib/chef/cookbook/chefignore.rb
@@ -1,6 +1,5 @@
-#--
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,25 +19,27 @@ class Chef
class Cookbook
class Chefignore
- COMMENTS_AND_WHITESPACE = /^\s*(?:#.*)?$/
+ COMMENTS_AND_WHITESPACE = /^\s*(?:#.*)?$/.freeze
attr_reader :ignores
def initialize(ignore_file_or_repo)
- # Check the 'ignore_file_or_repo' path first and then look in the parent directory
+ # Check the 'ignore_file_or_repo' path first and then look in the parent directories till root
# to handle both the chef repo cookbook layout and a standalone cookbook
@ignore_file = find_ignore_file(ignore_file_or_repo)
- @ignore_file = find_ignore_file(File.dirname(ignore_file_or_repo)) unless readable_file_or_symlink?(@ignore_file)
-
@ignores = parse_ignore_file
end
+ # @param [Array] file_list the list of cookbook files
+ # @return [Array] list of cookbook files with chefignore files removed
def remove_ignores_from(file_list)
Array(file_list).inject([]) do |unignored, file|
ignored?(file) ? unignored : unignored << file
end
end
+ # @param [String] file_name the file name to check ignored status for
+ # @return [Boolean] is the file ignored or not
def ignored?(file_name)
@ignores.any? { |glob| File.fnmatch?(glob, file_name) }
end
@@ -47,27 +48,34 @@ class Chef
def parse_ignore_file
ignore_globs = []
- if readable_file_or_symlink?(@ignore_file)
+ if @ignore_file && readable_file_or_symlink?(@ignore_file)
File.foreach(@ignore_file) do |line|
- ignore_globs << line.strip unless line =~ COMMENTS_AND_WHITESPACE
+ ignore_globs << line.strip unless COMMENTS_AND_WHITESPACE.match?(line)
end
else
- Chef::Log.debug("No chefignore file found at #@ignore_file no files will be ignored")
+ Chef::Log.debug("No chefignore file found. No files will be ignored!")
end
ignore_globs
end
+ # Lookup of chefignore file till the root dir of the provided path.
+ # If file refer then lookup the parent dir till the root.
+ # eg. path: /var/.chef/cookbook_name
+ # Lookup at '/var/.chef/cookbook_name/chefignore', '/var/.chef/chefignore' '/var/chefignore' and '/chefignore' until exist
def find_ignore_file(path)
- if File.basename(path) =~ /chefignore/
- path
- else
- File.join(path, "chefignore")
+ Pathname.new(path).ascend do |dir|
+ next unless dir.directory?
+
+ file = dir.join("chefignore")
+ return file.expand_path.to_s if file.exist?
end
+
+ nil
end
def readable_file_or_symlink?(path)
- File.exist?(@ignore_file) && File.readable?(@ignore_file) &&
- (File.file?(@ignore_file) || File.symlink?(@ignore_file))
+ File.exist?(path) && File.readable?(path) &&
+ (File.file?(path) || File.symlink?(path))
end
end
end
diff --git a/lib/chef/cookbook/cookbook_collection.rb b/lib/chef/cookbook/cookbook_collection.rb
index d06b8fd042..d8dae875e0 100644
--- a/lib/chef/cookbook/cookbook_collection.rb
+++ b/lib/chef/cookbook/cookbook_collection.rb
@@ -1,7 +1,6 @@
-#--
# Author:: Tim Hinderliter (<tim@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
-# Copyright:: Copyright 2010-2016 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,8 +16,8 @@
# limitations under the License.
#
-require "chef/mash"
-require "chef/cookbook/gem_installer"
+require_relative "../mash"
+require_relative "gem_installer"
class Chef
# == Chef::CookbookCollection
@@ -47,10 +46,10 @@ class Chef
# 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
+ # @raise [Chef::Exceptions::CookbookChefVersionMismatch] if the Chef::VERSION fails validation
+ # @raise [Chef::Exceptions::CookbookOhaiVersionMismatch] if the Ohai::VERSION fails validation
def validate!
- each do |cookbook_name, cookbook_version|
+ each_value do |cookbook_version|
cookbook_version.metadata.validate_chef_version!
cookbook_version.metadata.validate_ohai_version!
end
diff --git a/lib/chef/cookbook/cookbook_version_loader.rb b/lib/chef/cookbook/cookbook_version_loader.rb
index af8b2e043e..faed509321 100644
--- a/lib/chef/cookbook/cookbook_version_loader.rb
+++ b/lib/chef/cookbook/cookbook_version_loader.rb
@@ -1,28 +1,41 @@
-
-require "chef/cookbook_version"
-require "chef/cookbook/chefignore"
-require "chef/cookbook/metadata"
-require "chef/util/path_helper"
-require "find"
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../cookbook_version"
+require_relative "chefignore"
+require_relative "metadata"
+require_relative "../util/path_helper"
+require "find" unless defined?(Find.find)
class Chef
class Cookbook
+ # This class is only used directly from the Chef::CookbookLoader and from chef-fs,
+ # so it only affects legacy-mode chef-client runs and knife. It is not used by
+ # server or zolo/zero modes.
+ #
+ # This seems to be mostly a glorified factory method for creating CookbookVersion
+ # objects now, with creating Metadata objects bolted onto the side? It used
+ # to be also responsible for the merging of multiple objects when creating
+ # shadowed/merged cookbook versions from multiple sources. It also handles
+ # Chefignore files.
+ #
class CookbookVersionLoader
- FILETYPES_SUBJECT_TO_IGNORE = [ :attribute_filenames,
- :definition_filenames,
- :recipe_filenames,
- :template_filenames,
- :file_filenames,
- :library_filenames,
- :resource_filenames,
- :provider_filenames]
-
UPLOADED_COOKBOOK_VERSION_FILE = ".uploaded-cookbook-version.json".freeze
attr_reader :cookbook_settings
- attr_reader :cookbook_paths
- attr_reader :metadata_filenames
attr_reader :frozen
attr_reader :uploaded_cookbook_version_file
@@ -35,25 +48,14 @@ class Chef
def initialize(path, chefignore = nil)
@cookbook_path = File.expand_path( path ) # cookbook_path from which this was loaded
- # We keep a list of all cookbook paths that have been merged in
- @cookbook_paths = [ cookbook_path ]
@inferred_cookbook_name = File.basename( path )
@chefignore = chefignore
@metadata = nil
- @relative_path = /#{Regexp.escape(@cookbook_path)}\/(.+)$/
+ @relative_path = %r{#{Regexp.escape(cookbook_path)}/(.+)$}
@metadata_loaded = false
@cookbook_settings = {
- :all_files => {},
- :attribute_filenames => {},
- :definition_filenames => {},
- :recipe_filenames => {},
- :template_filenames => {},
- :file_filenames => {},
- :library_filenames => {},
- :resource_filenames => {},
- :provider_filenames => {},
- :root_filenames => {},
+ all_files: {},
}
@metadata_filenames = []
@@ -63,18 +65,24 @@ class Chef
# Load the cookbook. Raises an error if the cookbook_path given to the
# constructor doesn't point to a valid cookbook.
def load!
- file_paths_map = load
+ metadata # force lazy evaluation to occur
+
+ # re-raise any exception that occurred when reading the metadata
+ raise_metadata_error!
+
+ load_all_files
+
+ remove_ignored_files
if empty?
raise Exceptions::CookbookNotFoundInRepo, "The directory #{cookbook_path} does not contain a cookbook"
end
- file_paths_map
+
+ cookbook_settings
end
- # Load the cookbook. Does not raise an error if given a non-cookbook
- # directory as the cookbook_path. This behavior is provided for
- # compatibility, it is recommended to use #load! instead.
def load
+ Chef.deprecated(:internal_api, "Chef::Cookbook::CookbookVersionLoader's load method is deprecated. Please use load! instead.")
metadata # force lazy evaluation to occur
# re-raise any exception that occurred when reading the metadata
@@ -84,76 +92,26 @@ class Chef
remove_ignored_files
- load_as(:attribute_filenames, "attributes", "*.rb")
- load_as(:definition_filenames, "definitions", "*.rb")
- load_as(:recipe_filenames, "recipes", "*.rb")
- load_recursively_as(:library_filenames, "libraries", "*")
- load_recursively_as(:template_filenames, "templates", "*")
- load_recursively_as(:file_filenames, "files", "*")
- load_recursively_as(:resource_filenames, "resources", "*.rb")
- load_recursively_as(:provider_filenames, "providers", "*.rb")
- load_root_files
-
if empty?
Chef::Log.warn "Found a directory #{cookbook_name} in the cookbook path, but it contains no cookbook files. skipping."
end
- @cookbook_settings
+
+ cookbook_settings
end
alias :load_cookbooks :load
- def metadata_filenames
- return @metadata_filenames unless @metadata_filenames.empty?
- if File.exists?(File.join(cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE))
- @uploaded_cookbook_version_file = File.join(cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE)
- end
-
- if File.exists?(File.join(cookbook_path, "metadata.rb"))
- @metadata_filenames << File.join(cookbook_path, "metadata.rb")
- elsif File.exists?(File.join(cookbook_path, "metadata.json"))
- @metadata_filenames << File.join(cookbook_path, "metadata.json")
- elsif @uploaded_cookbook_version_file
- @metadata_filenames << @uploaded_cookbook_version_file
- end
-
- # Set frozen based on .uploaded-cookbook-version.json
- set_frozen
- @metadata_filenames
- end
-
def cookbook_version
return nil if empty?
- Chef::CookbookVersion.new(cookbook_name, *cookbook_paths).tap do |c|
+ Chef::CookbookVersion.new(cookbook_name, cookbook_path).tap do |c|
c.all_files = cookbook_settings[:all_files].values
- c.attribute_filenames = cookbook_settings[:attribute_filenames].values
- c.definition_filenames = cookbook_settings[:definition_filenames].values
- c.recipe_filenames = cookbook_settings[:recipe_filenames].values
- c.template_filenames = cookbook_settings[:template_filenames].values
- c.file_filenames = cookbook_settings[:file_filenames].values
- c.library_filenames = cookbook_settings[:library_filenames].values
- c.resource_filenames = cookbook_settings[:resource_filenames].values
- c.provider_filenames = cookbook_settings[:provider_filenames].values
- c.root_filenames = cookbook_settings[:root_filenames].values
- c.metadata_filenames = metadata_filenames
c.metadata = metadata
- c.freeze_version if @frozen
+ c.freeze_version if frozen
end
end
- def cookbook_name
- # The `name` attribute is now required in metadata, so
- # inferred_cookbook_name generally should not be used. Per CHEF-2923,
- # we have to not raise errors in cookbook metadata immediately, so that
- # users can still `knife cookbook upload some-cookbook` when an
- # unrelated cookbook has an error in its metadata. This situation
- # could prevent us from reading the `name` attribute from the metadata
- # entirely, but the name is used as a hash key in CookbookLoader, so we
- # fall back to the inferred name here.
- (metadata.name || @inferred_cookbook_name).to_sym
- end
-
# Generates the Cookbook::Metadata object
def metadata
return @metadata unless @metadata.nil?
@@ -164,7 +122,7 @@ class Chef
case metadata_file
when /\.rb$/
apply_ruby_metadata(metadata_file)
- when @uploaded_cookbook_version_file
+ when uploaded_cookbook_version_file
apply_json_cookbook_version_metadata(metadata_file)
when /\.json$/
apply_json_metadata(metadata_file)
@@ -185,36 +143,59 @@ class Chef
@metadata
end
+ def cookbook_name
+ # The `name` attribute is now required in metadata, so
+ # inferred_cookbook_name generally should not be used. Per CHEF-2923,
+ # we have to not raise errors in cookbook metadata immediately, so that
+ # users can still `knife cookbook upload some-cookbook` when an
+ # unrelated cookbook has an error in its metadata. This situation
+ # could prevent us from reading the `name` attribute from the metadata
+ # entirely, but the name is used as a hash key in CookbookLoader, so we
+ # fall back to the inferred name here.
+ (metadata.name || inferred_cookbook_name).to_sym
+ end
+
+ private
+
+ def metadata_filenames
+ return @metadata_filenames unless @metadata_filenames.empty?
+
+ if File.exists?(File.join(cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE))
+ @uploaded_cookbook_version_file = File.join(cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE)
+ end
+
+ if File.exists?(File.join(cookbook_path, "metadata.json"))
+ @metadata_filenames << File.join(cookbook_path, "metadata.json")
+ elsif File.exists?(File.join(cookbook_path, "metadata.rb"))
+ @metadata_filenames << File.join(cookbook_path, "metadata.rb")
+ elsif uploaded_cookbook_version_file
+ @metadata_filenames << uploaded_cookbook_version_file
+ end
+
+ # Set frozen based on .uploaded-cookbook-version.json
+ set_frozen
+ @metadata_filenames
+ end
+
def raise_metadata_error!
- raise @metadata_error unless @metadata_error.nil?
+ raise metadata_error unless metadata_error.nil?
+
# Metadata won't be valid if the cookbook is empty. If the cookbook is
# actually empty, a metadata error here would be misleading, so don't
# raise it (if called by #load!, a different error is raised).
if !empty? && !metadata.valid?
- message = "Cookbook loaded at path(s) [#{@cookbook_paths.join(', ')}] has invalid metadata: #{metadata.errors.join('; ')}"
+ message = "Cookbook loaded at path [#{cookbook_path}] has invalid metadata: #{metadata.errors.join("; ")}"
raise Exceptions::MetadataNotValid, message
end
false
end
def empty?
- cookbook_settings.values.all? { |files_hash| files_hash.empty? } && metadata_filenames.size == 0
- end
-
- def merge!(other_cookbook_loader)
- other_cookbook_settings = other_cookbook_loader.cookbook_settings
- cookbook_settings.each do |file_type, file_list|
- file_list.merge!(other_cookbook_settings[file_type])
- end
- metadata_filenames.concat(other_cookbook_loader.metadata_filenames)
- @cookbook_paths += other_cookbook_loader.cookbook_paths
- @frozen = true if other_cookbook_loader.frozen
- @metadata = nil # reset metadata so it gets reloaded and all metadata files applied.
- self
+ cookbook_settings.values.all?(&:empty?) && metadata_filenames.size == 0
end
def chefignore
- @chefignore ||= Chefignore.new(File.basename(cookbook_path))
+ @chefignore ||= Chefignore.new(cookbook_path)
end
# Enumerate all the files in a cookbook and assign the resulting list to
@@ -253,51 +234,6 @@ class Chef
end
end
- def load_root_files
- select_files_by_glob(File.join(Chef::Util::PathHelper.escape_glob_dir(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
- name = Chef::Util::PathHelper.relative_path_from(@cookbook_path, file)
- cookbook_settings[:root_filenames][name] = file
- end
- end
-
- def load_recursively_as(category, category_dir, glob)
- glob_pattern = File.join(Chef::Util::PathHelper.escape_glob_dir(cookbook_path, category_dir), "**", glob)
- select_files_by_glob(glob_pattern, File::FNM_DOTMATCH).each do |file|
- file = Chef::Util::PathHelper.cleanpath(file)
- name = Chef::Util::PathHelper.relative_path_from(@cookbook_path, file)
- cookbook_settings[category][name] = file
- end
- end
-
- def load_as(category, *path_glob)
- glob_pattern = File.join(Chef::Util::PathHelper.escape_glob_dir(cookbook_path), *path_glob)
- select_files_by_glob(glob_pattern).each do |file|
- file = Chef::Util::PathHelper.cleanpath(file)
- name = Chef::Util::PathHelper.relative_path_from(@cookbook_path, file)
- cookbook_settings[category][name] = file
- end
- end
-
- # Mimic Dir.glob inside a cookbook by running `File.fnmatch?` against
- # `cookbook_settings[:all_files]`.
- #
- # @param pattern [String] a glob string passed to `File.fnmatch?`
- # @param option [Integer] Option flag to control globbing behavior. These
- # are constants defined on `File`, such as `File::FNM_DOTMATCH`.
- # `File.fnmatch?` and `Dir.glob` only take one option argument, if you
- # need to combine options, you must `|` the constants together. To make
- # `File.fnmatch?` behave like `Dir.glob`, `File::FNM_PATHNAME` is
- # always enabled.
- def select_files_by_glob(pattern, option = 0)
- combined_opts = option | File::FNM_PATHNAME
- cookbook_settings[:all_files].values.select do |path|
- File.fnmatch?(pattern, path, combined_opts)
- end
- end
-
def remove_ignored_files
cookbook_settings[:all_files].reject! do |relative_path, full_path|
chefignore.ignored?(relative_path)
@@ -305,40 +241,34 @@ class Chef
end
def apply_ruby_metadata(file)
- begin
- @metadata.from_file(file)
- rescue Chef::Exceptions::JSON::ParseError
- Chef::Log.error("Error evaluating metadata.rb for #@inferred_cookbook_name in " + file)
- raise
- end
+ @metadata.from_file(file)
+ rescue Chef::Exceptions::JSON::ParseError
+ Chef::Log.error("Error evaluating metadata.rb for #{inferred_cookbook_name} in " + file)
+ raise
end
def apply_json_metadata(file)
- begin
- @metadata.from_json(IO.read(file))
- rescue Chef::Exceptions::JSON::ParseError
- Chef::Log.error("Couldn't parse cookbook metadata JSON for #@inferred_cookbook_name in " + file)
- raise
- end
+ @metadata.from_json(IO.read(file))
+ rescue Chef::Exceptions::JSON::ParseError
+ Chef::Log.error("Couldn't parse cookbook metadata JSON for #{inferred_cookbook_name} in " + file)
+ raise
end
def apply_json_cookbook_version_metadata(file)
- begin
- data = Chef::JSONCompat.parse(IO.read(file))
- @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
- # to load the cookbook. To keep compatibility, we fake it by setting
- # the metadata name from the cookbook version object's name.
- #
- # 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")
- rescue Chef::Exceptions::JSON::ParseError
- Chef::Log.error("Couldn't parse cookbook metadata JSON for #@inferred_cookbook_name in " + file)
- raise
- end
+ data = Chef::JSONCompat.parse(IO.read(file))
+ @metadata.from_hash(data["metadata"])
+ # the JSON cookbook 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
+ # to load the cookbook. To keep compatibility, we fake it by setting
+ # the metadata name from the cookbook version object's name.
+ #
+ # 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")
+ rescue Chef::Exceptions::JSON::ParseError
+ Chef::Log.error("Couldn't parse cookbook metadata JSON for #{inferred_cookbook_name} in " + file)
+ raise
end
def set_frozen
@@ -347,7 +277,7 @@ class Chef
data = Chef::JSONCompat.parse(IO.read(uploaded_cookbook_version_file))
@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}")
+ Chef::Log.error("Couldn't parse cookbook metadata JSON for #{inferred_cookbook_name} in #{uploaded_cookbook_version_file}")
raise
end
end
diff --git a/lib/chef/cookbook/file_system_file_vendor.rb b/lib/chef/cookbook/file_system_file_vendor.rb
index 91f1c9f853..a4a6711270 100644
--- a/lib/chef/cookbook/file_system_file_vendor.rb
+++ b/lib/chef/cookbook/file_system_file_vendor.rb
@@ -1,7 +1,6 @@
-#--
# Author:: Christopher Walters (<cw@chef.io>)
# Author:: Tim Hinderliter (<tim@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,7 +16,7 @@
# limitations under the License.
#
-require "chef/cookbook/file_vendor"
+require_relative "file_vendor"
class Chef
class Cookbook
@@ -28,28 +27,28 @@ class Chef
# and throws the rest away then re-builds the list of files on the
# disk. This is due to the manifest not having the on-disk file
# locations, since in the chef-client case, that information is
- # non-sensical.
+ # nonsensical.
class FileSystemFileVendor < FileVendor
attr_reader :cookbook_name
attr_reader :repo_paths
def initialize(manifest, *repo_paths)
- @cookbook_name = manifest[:cookbook_name]
+ @cookbook_name = manifest.name
@repo_paths = repo_paths.flatten
- raise ArgumentError, "You must specify at least one repo path" if @repo_paths.empty?
+ raise ArgumentError, "You must specify at least one repo path" if repo_paths.empty?
+ end
+
+ def cookbooks
+ @cookbooks ||= Chef::CookbookLoader.new(repo_paths).load_cookbooks
end
# Implements abstract base's requirement. It looks in the
# Chef::Config.cookbook_path file hierarchy for the requested
# file.
def get_filename(filename)
- location = @repo_paths.inject(nil) do |memo, basepath|
- candidate_location = File.join(basepath, @cookbook_name, filename)
- memo = candidate_location if File.exist?(candidate_location)
- memo
- end
- raise "File #{filename} does not exist for cookbook #{@cookbook_name}" unless location
+ location = File.join(cookbooks[cookbook_name].root_dir, filename) if cookbooks.key?(cookbook_name)
+ raise "File #{filename} does not exist for cookbook #{cookbook_name}" unless location && File.exist?(location)
location
end
diff --git a/lib/chef/cookbook/file_vendor.rb b/lib/chef/cookbook/file_vendor.rb
index f849f79296..2eecf0a789 100644
--- a/lib/chef/cookbook/file_vendor.rb
+++ b/lib/chef/cookbook/file_vendor.rb
@@ -1,7 +1,6 @@
-#
# Author:: Christopher Walters (<cw@chef.io>)
# Author:: Tim Hinderliter (<tim@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -55,6 +54,7 @@ class Chef
if @vendor_class.nil?
raise "Must configure FileVendor to use a specific implementation before creating an instance"
end
+
@vendor_class.new(manifest, @initialization_options)
end
diff --git a/lib/chef/cookbook/gem_installer.rb b/lib/chef/cookbook/gem_installer.rb
index df73ce3ee4..d7c18627de 100644
--- a/lib/chef/cookbook/gem_installer.rb
+++ b/lib/chef/cookbook/gem_installer.rb
@@ -1,5 +1,4 @@
-#--
-# Copyright:: Copyright (c) 2010-2016 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,8 +14,8 @@
# limitations under the License.
#
-require "tmpdir"
-require "chef/mixin/shell_out"
+require "tmpdir" unless defined?(Dir.mktmpdir)
+require_relative "../mixin/shell_out"
class Chef
class Cookbook
@@ -36,10 +35,20 @@ class Chef
# Installs the gems into the omnibus gemset.
#
def install
- cookbook_gems = []
+ cookbook_gems = Hash.new { |h, k| h[k] = [] }
- cookbook_collection.each do |cookbook_name, cookbook_version|
- cookbook_gems += cookbook_version.metadata.gems
+ cookbook_collection.each_value do |cookbook_version|
+ cookbook_version.metadata.gems.each do |args|
+ if cookbook_gems[args.first].last.is_a?(Hash)
+ args << {} unless args.last.is_a?(Hash)
+ args.last.merge!(cookbook_gems[args.first].pop) do |key, v1, v2|
+ raise Chef::Exceptions::GemRequirementConflict.new(args.first, key, v1, v2) if v1 != v2
+
+ v2
+ end
+ end
+ cookbook_gems[args.first] += args[1..]
+ end
end
events.cookbook_gem_start(cookbook_gems)
@@ -48,15 +57,22 @@ class Chef
begin
Dir.mktmpdir("chef-gem-bundle") do |dir|
File.open("#{dir}/Gemfile", "w+") do |tf|
- tf.puts "source '#{Chef::Config[:rubygems_url]}'"
- cookbook_gems.each do |args|
- tf.puts "gem(*#{args.inspect})"
+ Array(Chef::Config[:rubygems_url] || "https://rubygems.org").each do |s|
+ tf.puts "source '#{s}'"
+ end
+ cookbook_gems.each do |gem_name, args|
+ tf.puts "gem(*#{([gem_name] + args).inspect})"
end
tf.close
- Chef::Log.debug("generated Gemfile contents:")
- Chef::Log.debug(IO.read(tf.path))
- so = shell_out!("bundle install", cwd: dir, env: { "PATH" => path_with_prepended_ruby_bin })
- Chef::Log.info(so.stdout)
+ Chef::Log.trace("generated Gemfile contents:")
+ Chef::Log.trace(IO.read(tf.path))
+ # Skip installation only if Chef::Config[:skip_gem_metadata_installation] option is true
+ unless Chef::Config[:skip_gem_metadata_installation]
+ # Add additional options to bundle install
+ cmd = [ "bundle", "install", Chef::Config[:gem_installer_bundler_options] ]
+ so = shell_out!(cmd, cwd: dir, env: { "PATH" => path_with_prepended_ruby_bin })
+ Chef::Log.info(so.stdout)
+ end
end
end
Gem.clear_paths
diff --git a/lib/chef/cookbook/manifest_v0.rb b/lib/chef/cookbook/manifest_v0.rb
new file mode 100644
index 0000000000..b2b39f32f3
--- /dev/null
+++ b/lib/chef/cookbook/manifest_v0.rb
@@ -0,0 +1,74 @@
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../json_compat"
+require_relative "../mixin/versioned_api"
+
+class Chef
+ class Cookbook
+ class ManifestV0
+ extend Chef::Mixin::VersionedAPI
+
+ minimum_api_version 0
+
+ COOKBOOK_SEGMENTS = %w{ resources providers recipes definitions libraries attributes files templates root_files }.freeze
+
+ class << self
+
+ def from_hash(hash)
+ response = Mash.new(hash)
+ response[:all_files] = COOKBOOK_SEGMENTS.inject([]) do |memo, segment|
+ next memo if hash[segment].nil? || hash[segment].empty?
+
+ hash[segment].each do |file|
+ file["name"] = "#{segment}/#{file["name"]}"
+ memo << file
+ end
+ response.delete(segment)
+ memo
+ end
+ response
+ end
+
+ def to_h(manifest)
+ result = manifest.manifest.dup
+ result.delete("all_files")
+
+ files = manifest.by_parent_directory
+ files.keys.each_with_object(result) do |parent, memo|
+ if COOKBOOK_SEGMENTS.include?(parent)
+ memo[parent] ||= []
+ files[parent].each do |file|
+ file["name"] = file["name"].split("/")[1]
+ file.delete("full_path")
+ memo[parent] << file
+ end
+ end
+ end
+ # Ensure all segments are set to [] if they don't exist.
+ # See https://github.com/chef/chef/issues/6044
+ COOKBOOK_SEGMENTS.each do |segment|
+ result[segment] ||= []
+ end
+
+ result.merge({ "frozen?" => manifest.frozen_version?, "chef_type" => "cookbook_version" })
+ end
+
+ alias_method :to_hash, :to_h
+ end
+ end
+ end
+end
diff --git a/lib/chef/cookbook/manifest_v2.rb b/lib/chef/cookbook/manifest_v2.rb
new file mode 100644
index 0000000000..a502142e17
--- /dev/null
+++ b/lib/chef/cookbook/manifest_v2.rb
@@ -0,0 +1,45 @@
+# Copyright:: Copyright (c) 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_relative "../json_compat"
+require_relative "../mixin/versioned_api"
+
+class Chef
+ class Cookbook
+ class ManifestV2
+ extend Chef::Mixin::VersionedAPI
+
+ minimum_api_version 2
+
+ class << self
+ def from_hash(hash)
+ Chef::Log.trace "processing manifest: #{hash}"
+ Mash.new hash
+ end
+
+ def to_h(manifest)
+ result = manifest.manifest.dup
+ result["all_files"].map! { |file| file.delete("full_path"); file }
+ result["frozen?"] = manifest.frozen_version?
+ result["chef_type"] = "cookbook_version"
+ result.to_hash
+ end
+
+ alias_method :to_hash, :to_h
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/cookbook/metadata.rb b/lib/chef/cookbook/metadata.rb
index ab83da9e55..7f6d972771 100644
--- a/lib/chef/cookbook/metadata.rb
+++ b/lib/chef/cookbook/metadata.rb
@@ -1,8 +1,7 @@
-#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: AJ Christensen (<aj@chef.io>)
# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,15 +17,15 @@
# 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/version_constraint/platform"
-require "chef/json_compat"
+require_relative "../exceptions"
+require_relative "../mash"
+require_relative "../mixin/from_file"
+require_relative "../mixin/params_validate"
+require_relative "../log"
+require_relative "../version_class"
+require_relative "../version_constraint"
+require_relative "../version_constraint/platform"
+require_relative "../json_compat"
class Chef
class Cookbook
@@ -44,13 +43,7 @@ class Chef
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
@@ -59,37 +52,26 @@ class Chef
CHEF_VERSIONS = "chef_versions".freeze
OHAI_VERSIONS = "ohai_versions".freeze
GEMS = "gems".freeze
+ EAGER_LOAD_LIBRARIES = "eager_load_libraries".freeze
+
+ COMPARISON_FIELDS = %i{name description long_description maintainer
+ maintainer_email license platforms dependencies
+ providing recipes version source_url issues_url
+ privacy chef_versions ohai_versions gems
+ eager_load_libraries}.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, :privacy, :chef_versions, :ohai_versions,
- :gems ]
-
- VERSION_CONSTRAINTS = { :depends => DEPENDENCIES,
- :recommends => RECOMMENDATIONS,
- :suggests => SUGGESTIONS,
- :conflicts => CONFLICTING,
- :provides => PROVIDING,
- :replaces => REPLACING,
- :chef_version => CHEF_VERSIONS,
- :ohai_version => OHAI_VERSIONS }
+ VERSION_CONSTRAINTS = { depends: DEPENDENCIES,
+ provides: PROVIDING,
+ chef_version: CHEF_VERSIONS,
+ ohai_version: OHAI_VERSIONS }.freeze
include Chef::Mixin::ParamsValidate
include Chef::Mixin::FromFile
attr_reader :platforms
attr_reader :dependencies
- attr_reader :recommendations
- attr_reader :suggestions
- attr_reader :conflicting
attr_reader :providing
- attr_reader :replacing
- attr_reader :attributes
- attr_reader :groupings
attr_reader :recipes
- attr_reader :version
# @return [Array<Gem::Dependency>] Array of supported Chef versions
attr_reader :chef_versions
@@ -115,18 +97,12 @@ class Chef
@long_description = ""
@license = "All rights reserved"
- @maintainer = nil
- @maintainer_email = nil
+ @maintainer = ""
+ @maintainer_email = ""
@platforms = Mash.new
@dependencies = Mash.new
- @recommendations = Mash.new
- @suggestions = Mash.new
- @conflicting = Mash.new
@providing = Mash.new
- @replacing = Mash.new
- @attributes = Mash.new
- @groupings = Mash.new
@recipes = Mash.new
@version = Version.new("0.0.0")
@source_url = ""
@@ -135,6 +111,7 @@ class Chef
@chef_versions = []
@ohai_versions = []
@gems = []
+ @eager_load_libraries = true
@errors = []
end
@@ -182,7 +159,7 @@ class Chef
set_or_return(
:maintainer,
arg,
- :kind_of => [ String ]
+ kind_of: [ String ]
)
end
@@ -197,7 +174,7 @@ class Chef
set_or_return(
:maintainer_email,
arg,
- :kind_of => [ String ]
+ kind_of: [ String ]
)
end
@@ -212,7 +189,7 @@ class Chef
set_or_return(
:license,
arg,
- :kind_of => [ String ]
+ kind_of: [ String ]
)
end
@@ -227,7 +204,7 @@ class Chef
set_or_return(
:description,
arg,
- :kind_of => [ String ]
+ kind_of: [ String ]
)
end
@@ -242,7 +219,7 @@ class Chef
set_or_return(
:long_description,
arg,
- :kind_of => [ String ]
+ kind_of: [ String ]
)
end
@@ -273,7 +250,7 @@ class Chef
set_or_return(
:name,
arg,
- :kind_of => [ String ]
+ kind_of: [ String ]
)
end
@@ -306,64 +283,14 @@ class Chef
# versions<Array>:: Returns the list of versions for the platform
def depends(cookbook, *version_args)
if cookbook == name
- Chef::Log.warn "Ignoring self-dependency in cookbook #{name}, please remove it (in the future this will be fatal)."
+ raise "Cookbook depends on itself in cookbook #{name}, please remove the this unnecessary self-dependency"
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
-
- # Adds a recommendation for another cookbook, with version checking strings.
- #
- # === Parameters
- # cookbook<String>:: The cookbook
- # version<String>:: A version constraint of the form "OP VERSION",
- # where OP is one of < <= = > >= ~> and VERSION has
- # the form x.y.z or x.y.
- #
- # === Returns
- # versions<Array>:: Returns the list of versions for the platform
- def recommends(cookbook, *version_args)
- version = new_args_format(:recommends, cookbook, version_args)
- constraint = validate_version_constraint(:recommends, cookbook, version)
- @recommendations[cookbook] = constraint.to_s
- @recommendations[cookbook]
- end
- # Adds a suggestion for another cookbook, with version checking strings.
- #
- # === Parameters
- # cookbook<String>:: The cookbook
- # version<String>:: A version constraint of the form "OP VERSION",
- # where OP is one of < <= = > >= ~> and VERSION has the
- # formx.y.z or x.y.
- #
- # === Returns
- # versions<Array>:: Returns the list of versions for the platform
- def suggests(cookbook, *version_args)
- version = new_args_format(:suggests, cookbook, version_args)
- constraint = validate_version_constraint(:suggests, cookbook, version)
- @suggestions[cookbook] = constraint.to_s
- @suggestions[cookbook]
- end
-
- # Adds a conflict for another cookbook, with version checking strings.
- #
- # === Parameters
- # cookbook<String>:: The cookbook
- # version<String>:: A version constraint of the form "OP VERSION",
- # where OP is one of < <= = > >= ~> and VERSION has
- # the form x.y.z or x.y.
- #
- # === Returns
- # versions<Array>:: Returns the list of versions for the platform
- def conflicts(cookbook, *version_args)
- version = new_args_format(:conflicts, cookbook, version_args)
- constraint = validate_version_constraint(:conflicts, cookbook, version)
- @conflicting[cookbook] = constraint.to_s
- @conflicting[cookbook]
+ @dependencies[cookbook]
end
# Adds a recipe, definition, or resource provided by this cookbook.
@@ -387,22 +314,6 @@ class Chef
@providing[cookbook]
end
- # Adds a cookbook that is replaced by this one, with version checking strings.
- #
- # === Parameters
- # cookbook<String>:: The cookbook we replace
- # version<String>:: A version constraint of the form "OP VERSION",
- # where OP is one of < <= = > >= ~> and VERSION has the form x.y.z or x.y.
- #
- # === Returns
- # versions<Array>:: Returns the list of versions for the platform
- def replaces(cookbook, *version_args)
- version = new_args_format(:replaces, cookbook, version_args)
- constraint = validate_version_constraint(:replaces, cookbook, version)
- @replacing[cookbook] = constraint.to_s
- @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.
@@ -436,6 +347,25 @@ class Chef
@gems
end
+ # Metadata DSL to control the behavior of library loading.
+ #
+ # Can be set to:
+ #
+ # true - libraries are eagerly loaded in alphabetical order (backcompat)
+ # false - libraries are not eagerly loaded, the libraries dir is added to the LOAD_PATH
+ # String - a file or glob pattern to eagerly load, otherwise it is treated like `false`
+ # Array<String> - array of files or globs to eagerly load, otherwise it is treated like `false`
+ #
+ # @params arg [Array,String,TrueClass,FalseClass]
+ # @params [Array,TrueClass,FalseClass]
+ def eager_load_libraries(arg = nil)
+ set_or_return(
+ :eager_load_libraries,
+ arg,
+ kind_of: [ Array, String, TrueClass, FalseClass ]
+ )
+ end
+
# Adds a description for a recipe.
#
# === Parameters
@@ -461,8 +391,8 @@ class Chef
def recipes_from_cookbook_version(cookbook)
cookbook.fully_qualified_recipe_names.map do |recipe_name|
unqualified_name =
- if recipe_name =~ /::default$/
- self.name.to_s
+ if /::default$/.match?(recipe_name)
+ name.to_s
else
recipe_name
end
@@ -474,67 +404,10 @@ class Chef
end
end
- # Adds an attribute that a user needs to configure for this cookbook. Takes
- # a name (with the / notation for a nested attribute), followed by any of
- # these options
- #
- # display_name<String>:: What a UI should show for this attribute
- # description<String>:: A hint as to what this attr is for
- # choice<Array>:: An array of choices to present to the user.
- # calculated<Boolean>:: If true, the default value is calculated by the recipe and cannot be displayed.
- # type<String>:: "string" or "array" - default is "string" ("hash" is supported for backwards compatibility)
- # required<String>:: Whether this attr is 'required', 'recommended' or 'optional' - default 'optional' (true/false values also supported for backwards compatibility)
- # recipes<Array>:: An array of recipes which need this attr set.
- # default<String>,<Array>,<Hash>:: The default value
- #
- # === Parameters
- # name<String>:: The name of the attribute ('foo', or 'apache2/log_dir')
- # options<Hash>:: The description of the options
- #
- # === Returns
- # options<Hash>:: Returns the current options hash
- def attribute(name, options)
- validate(
- options,
- {
- :display_name => { :kind_of => String },
- :description => { :kind_of => String },
- :choice => { :kind_of => [ Array ], :default => [] },
- :calculated => { :equal_to => [ true, false ], :default => false },
- :type => { :equal_to => %w{string array hash symbol boolean numeric}, :default => "string" },
- :required => { :equal_to => [ "required", "recommended", "optional", true, false ], :default => "optional" },
- :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 },
- :privacy => { :kind_of => [ TrueClass, FalseClass ] },
- }
- )
- options[:required] = remap_required_attribute(options[:required]) unless options[:required].nil?
- validate_choice_array(options)
- validate_calculated_default_rule(options)
- validate_choice_default_rule(options)
-
- @attributes[name] = options
- @attributes[name]
- end
-
- def grouping(name, options)
- validate(
- options,
- {
- :title => { :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#to_s is not useful, and there is no #to_json defined on it or its component
+ # objects, so we have to write our own rendering method.
#
# [ Gem::Dependency.new(">= 12.5"), Gem::Dependency.new(">= 11.18.0", "< 12.0") ]
#
@@ -565,84 +438,76 @@ class Chef
end
end
- def to_hash
+ def to_h
{
- NAME => self.name,
- DESCRIPTION => self.description,
- LONG_DESCRIPTION => self.long_description,
- MAINTAINER => self.maintainer,
- MAINTAINER_EMAIL => self.maintainer_email,
- LICENSE => self.license,
- PLATFORMS => self.platforms,
- DEPENDENCIES => self.dependencies,
- RECOMMENDATIONS => self.recommendations,
- SUGGESTIONS => self.suggestions,
- CONFLICTING => self.conflicting,
- PROVIDING => self.providing,
- REPLACING => self.replacing,
- ATTRIBUTES => self.attributes,
- GROUPINGS => self.groupings,
- RECIPES => self.recipes,
- VERSION => self.version,
- SOURCE_URL => self.source_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),
- GEMS => self.gems,
+ NAME => name,
+ DESCRIPTION => description,
+ LONG_DESCRIPTION => long_description,
+ MAINTAINER => maintainer,
+ MAINTAINER_EMAIL => maintainer_email,
+ LICENSE => license,
+ PLATFORMS => platforms,
+ DEPENDENCIES => dependencies,
+ PROVIDING => providing,
+ RECIPES => recipes,
+ VERSION => version,
+ SOURCE_URL => source_url,
+ ISSUES_URL => issues_url,
+ PRIVACY => privacy,
+ CHEF_VERSIONS => gem_requirements_to_array(*chef_versions),
+ OHAI_VERSIONS => gem_requirements_to_array(*ohai_versions),
+ GEMS => gems,
+ EAGER_LOAD_LIBRARIES => eager_load_libraries,
}
end
+ alias_method :to_hash, :to_h
+
def to_json(*a)
- Chef::JSONCompat.to_json(to_hash, *a)
+ Chef::JSONCompat.to_json(to_h, *a)
end
def self.from_hash(o)
- cm = self.new()
+ cm = new
cm.from_hash(o)
cm
end
def from_hash(o)
- @name = o[NAME] if o.has_key?(NAME)
- @description = o[DESCRIPTION] if o.has_key?(DESCRIPTION)
- @long_description = o[LONG_DESCRIPTION] if o.has_key?(LONG_DESCRIPTION)
- @maintainer = o[MAINTAINER] if o.has_key?(MAINTAINER)
- @maintainer_email = o[MAINTAINER_EMAIL] if o.has_key?(MAINTAINER_EMAIL)
- @license = o[LICENSE] if o.has_key?(LICENSE)
- @platforms = o[PLATFORMS] if o.has_key?(PLATFORMS)
- @dependencies = handle_deprecated_constraints(o[DEPENDENCIES]) if o.has_key?(DEPENDENCIES)
- @recommendations = handle_deprecated_constraints(o[RECOMMENDATIONS]) if o.has_key?(RECOMMENDATIONS)
- @suggestions = handle_deprecated_constraints(o[SUGGESTIONS]) if o.has_key?(SUGGESTIONS)
- @conflicting = handle_deprecated_constraints(o[CONFLICTING]) if o.has_key?(CONFLICTING)
- @providing = o[PROVIDING] if o.has_key?(PROVIDING)
- @replacing = handle_deprecated_constraints(o[REPLACING]) if o.has_key?(REPLACING)
- @attributes = o[ATTRIBUTES] if o.has_key?(ATTRIBUTES)
- @groupings = o[GROUPINGS] if o.has_key?(GROUPINGS)
- @recipes = o[RECIPES] if o.has_key?(RECIPES)
- @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)
- @gems = o[GEMS] if o.has_key?(GEMS)
+ @name = o[NAME] if o.key?(NAME)
+ @description = o[DESCRIPTION] if o.key?(DESCRIPTION)
+ @long_description = o[LONG_DESCRIPTION] if o.key?(LONG_DESCRIPTION)
+ @maintainer = o[MAINTAINER] if o.key?(MAINTAINER)
+ @maintainer_email = o[MAINTAINER_EMAIL] if o.key?(MAINTAINER_EMAIL)
+ @license = o[LICENSE] if o.key?(LICENSE)
+ @platforms = o[PLATFORMS] if o.key?(PLATFORMS)
+ @dependencies = handle_incorrect_constraints(o[DEPENDENCIES]) if o.key?(DEPENDENCIES)
+ @providing = o[PROVIDING] if o.key?(PROVIDING)
+ @recipes = o[RECIPES] if o.key?(RECIPES)
+ @version = o[VERSION] if o.key?(VERSION)
+ @source_url = o[SOURCE_URL] if o.key?(SOURCE_URL)
+ @issues_url = o[ISSUES_URL] if o.key?(ISSUES_URL)
+ @privacy = o[PRIVACY] if o.key?(PRIVACY)
+ @chef_versions = gem_requirements_from_array("chef", o[CHEF_VERSIONS]) if o.key?(CHEF_VERSIONS)
+ @ohai_versions = gem_requirements_from_array("ohai", o[OHAI_VERSIONS]) if o.key?(OHAI_VERSIONS)
+ @gems = o[GEMS] if o.key?(GEMS)
+ @eager_load_libraries = o[EAGER_LOAD_LIBRARIES] if o.key?(EAGER_LOAD_LIBRARIES)
self
end
def self.from_json(string)
o = Chef::JSONCompat.from_json(string)
- self.from_hash(o)
+ from_hash(o)
end
def self.validate_json(json_str)
o = Chef::JSONCompat.from_json(json_str)
- metadata = new()
+ metadata = new
VERSION_CONSTRAINTS.each do |dependency_type, hash_key|
if dependency_group = o[hash_key]
dependency_group.each do |cb_name, constraints|
- if metadata.respond_to?(method_name)
- metadata.public_send(method_name, cb_name, *Array(constraints))
+ if metadata.respond_to?(dependency_type)
+ metadata.public_send(dependency_type, cb_name, *Array(constraints))
end
end
end
@@ -666,10 +531,29 @@ class Chef
set_or_return(
:source_url,
arg,
- :kind_of => [ String ]
+ kind_of: [ String ]
)
end
+ # This method translates version constraint strings from
+ # cookbooks with the old format.
+ #
+ # Before we began respecting version constraints, we allowed
+ # multiple constraints to be placed on cookbooks, as well as the
+ # << and >> operators, which are now just < and >. For
+ # specifications with more than one constraint, we return an
+ # empty array (otherwise, we're silently abiding only part of
+ # the contract they have specified to us). If there is only one
+ # constraint, we are replacing the old << and >> with the new <
+ # and >.
+ def handle_incorrect_constraints(specification)
+ specification.inject(Mash.new) do |acc, (cb, constraints)|
+ constraints = Array(constraints)
+ acc[cb] = (constraints.empty? || constraints.size > 1) ? [] : constraints.first
+ acc
+ end
+ end
+
# Sets the cookbook's issues URL, or returns it.
#
# === Parameters
@@ -681,7 +565,7 @@ class Chef
set_or_return(
:issues_url,
arg,
- :kind_of => [ String ]
+ kind_of: [ String ]
)
end
@@ -698,14 +582,14 @@ class Chef
set_or_return(
:privacy,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ 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
+ # @raise [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)
@@ -715,13 +599,21 @@ class Chef
# 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
+ # @raise [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
+ def method_missing(method, *args, &block)
+ if block_given?
+ super
+ else
+ Chef::Log.trace "ignoring method #{method} on cookbook with name #{name}, possible typo or the ghosts of metadata past or future?"
+ end
+ end
+
private
# Helper to match a gem style version (ohai_version/chef_version) against a set of
@@ -735,6 +627,7 @@ class Chef
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
@@ -751,15 +644,15 @@ class Chef
elsif version_constraints.size == 1
version_constraints.first
else
- 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 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:
-#{caller[0...5].map { |line| " " + line }.join("\n")}
-OBSOLETED
+ 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 https://docs.chef.io/config_rb_metadata/ for the updated syntax.
+
+ Called by: #{caller_name} '#{dep_name}', #{version_constraints.map(&:inspect).join(", ")}
+ Called from:
+ #{caller[0...5].map { |line| " " + line }.join("\n")}
+ OBSOLETED
raise Exceptions::ObsoleteDependencySyntax, msg
end
end
@@ -769,16 +662,17 @@ OBSOLETED
rescue Chef::Exceptions::InvalidVersionConstraint => e
Log.debug(e)
- msg = <<-INVALID
-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 https://docs.chef.io/config_rb_metadata.html for more information.
-
-Called by: #{caller_name} '#{dep_name}', '#{constraint_str}'
-Called from:
-#{caller[0...5].map { |line| " " + line }.join("\n")}
-INVALID
+ msg = <<~INVALID
+ The version constraint syntax you are using is not valid. If you recently
+ upgraded from Chef Infra releases before 0.10, be aware that you no may
+ longer use "<<" and ">>" for 'less than' and 'greater than'; use '<' and
+ '>' instead. Consult https://docs.chef.io/config_rb_metadata/ for more
+ information.
+
+ Called by: #{caller_name} '#{dep_name}', '#{constraint_str}'
+ Called from:
+ #{caller[0...5].map { |line| " " + line }.join("\n")}
+ INVALID
raise Exceptions::InvalidVersionConstraint, msg
end
@@ -789,9 +683,9 @@ INVALID
# === Parameters
# arry<Array>:: An array to be validated
def validate_string_array(arry)
- if arry.kind_of?(Array)
+ if arry.is_a?(Array)
arry.each do |choice|
- validate( { :choice => choice }, { :choice => { :kind_of => String } } )
+ validate( { choice: choice }, { choice: { kind_of: String } } )
end
end
end
@@ -802,7 +696,7 @@ INVALID
# === Parameters
# opts<Hash>:: The options hash
def validate_choice_array(opts)
- if opts[:choice].kind_of?(Array)
+ if opts[:choice].is_a?(Array)
case opts[:type]
when "string"
validator = [ String ]
@@ -819,7 +713,7 @@ INVALID
end
opts[:choice].each do |choice|
- validate( { :choice => choice }, { :choice => { :kind_of => validator } } )
+ validate( { choice: choice }, { choice: { kind_of: validator } } )
end
end
end
@@ -854,35 +748,15 @@ INVALID
return if !options[:choice].is_a?(Array) || options[:choice].empty?
if options[:default].is_a?(String) && options[:default] != ""
- raise ArgumentError, "Default must be one of your choice values!" if options[:choice].index(options[:default]) == nil
+ raise ArgumentError, "Default must be one of your choice values!" if options[:choice].index(options[:default]).nil?
end
if options[:default].is_a?(Array) && !options[:default].empty?
options[:default].each do |val|
- raise ArgumentError, "Default values must be a subset of your choice values!" if options[:choice].index(val) == nil
+ raise ArgumentError, "Default values must be a subset of your choice values!" if options[:choice].index(val).nil?
end
end
end
-
- # This method translates version constraint strings from
- # cookbooks with the old format.
- #
- # Before we began respecting version constraints, we allowed
- # multiple constraints to be placed on cookbooks, as well as the
- # << and >> operators, which are now just < and >. For
- # specifications with more than one constraint, we return an
- # empty array (otherwise, we're silently abiding only part of
- # the contract they have specified to us). If there is only one
- # constraint, we are replacing the old << and >> with the new <
- # and >.
- 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
- end
- end
-
end
#== Chef::Cookbook::MinimalMetadata
diff --git a/lib/chef/cookbook/remote_file_vendor.rb b/lib/chef/cookbook/remote_file_vendor.rb
index e63d094dc4..a6a6e96e06 100644
--- a/lib/chef/cookbook/remote_file_vendor.rb
+++ b/lib/chef/cookbook/remote_file_vendor.rb
@@ -1,6 +1,5 @@
-#
# Author:: Tim Hinderliter (<tim@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,7 +15,7 @@
# limitations under the License.
#
-require "chef/cookbook/file_vendor"
+require_relative "file_vendor"
class Chef
class Cookbook
@@ -30,7 +29,7 @@ class Chef
def initialize(manifest, rest)
@manifest = manifest
- @cookbook_name = @manifest[:cookbook_name] || @manifest[:name]
+ @cookbook_name = @manifest.name
@rest = rest
end
@@ -38,14 +37,15 @@ class Chef
# Chef::Config.cookbook_path file hierarchy for the requested
# file.
def get_filename(filename)
- if filename =~ /([^\/]+)\/(.+)$/
+ if filename =~ %r{([^/]+)/(.+)$}
segment = $1
else
raise "get_filename: Cannot determine segment/filename for incoming filename #{filename}"
end
- raise "No such segment #{segment} in cookbook #{@cookbook_name}" unless @manifest[segment]
- found_manifest_record = @manifest[segment].find { |manifest_record| manifest_record[:path] == filename }
+ raise "No such segment #{segment} in cookbook #{@cookbook_name}" unless @manifest.files_for(segment)
+
+ found_manifest_record = @manifest.files_for(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"])
@@ -55,7 +55,7 @@ class Chef
validate_cached_copy(cache_filename)
current_checksum = nil
- if Chef::FileCache.has_key?(cache_filename)
+ if Chef::FileCache.key?(cache_filename)
current_checksum = Chef::CookbookVersion.checksum_cookbook_file(Chef::FileCache.load(cache_filename, false))
end
@@ -65,17 +65,15 @@ class Chef
if current_checksum != found_manifest_record["checksum"]
raw_file = @rest.streaming_request(found_manifest_record[:url])
- Chef::Log.debug("Storing updated #{cache_filename} in the cache.")
+ Chef::Log.trace("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.trace("Not fetching #{cache_filename}, as the cache is up to date.")
+ Chef::Log.trace("Current checksum: #{current_checksum}; manifest checksum: #{found_manifest_record["checksum"]})")
end
- full_path_cache_filename = Chef::FileCache.load(cache_filename, false)
-
# return the filename, not the contents (second argument= false)
- full_path_cache_filename
+ Chef::FileCache.load(cache_filename, false)
end
def validate_cached_copy(cache_filename)
diff --git a/lib/chef/cookbook/synchronizer.rb b/lib/chef/cookbook/synchronizer.rb
index bb44bc3d5c..53e874d0e8 100644
--- a/lib/chef/cookbook/synchronizer.rb
+++ b/lib/chef/cookbook/synchronizer.rb
@@ -1,7 +1,23 @@
-require "chef/client"
-require "chef/util/threaded_job_queue"
-require "chef/server_api"
-require "singleton"
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../client"
+require_relative "../util/threaded_job_queue"
+require_relative "../server_api"
+require "singleton" unless defined?(Singleton)
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
@@ -31,6 +47,7 @@ class Chef
end
def reset!
+ @skip_removal = nil
@valid_cache_entries = {}
end
@@ -48,7 +65,7 @@ class Chef
# manifest.
cache.find(File.join(%w{cookbooks ** {*,.*}})).each do |cache_filename|
unless @valid_cache_entries[cache_filename]
- Chef::Log.info("Removing #{cache_filename} from the cache; it is no longer needed by chef-client.")
+ Chef::Log.info("Removing #{cache_filename} from the cache; it is no longer needed by #{ChefUtils::Dist::Infra::CLIENT}.")
cache.delete(cache_filename)
end
end
@@ -62,22 +79,17 @@ class Chef
# Synchronizes the locally cached copies of cookbooks with the files on the
# server.
class CookbookSynchronizer
- CookbookFile = Struct.new(:cookbook, :segment, :manifest_record)
+ CookbookFile = Struct.new(:cookbook, :manifest_record)
attr_accessor :remove_obsoleted_files
def initialize(cookbooks_by_name, events)
- @eager_segments = Chef::CookbookVersion::COOKBOOK_SEGMENTS.dup
- unless Chef::Config[:no_lazy_load]
- @eager_segments.delete(:files)
- @eager_segments.delete(:templates)
- end
- @eager_segments.freeze
-
@cookbooks_by_name, @events = cookbooks_by_name, events
@cookbook_full_file_paths = {}
@remove_obsoleted_files = true
+
+ @lazy_files = {}
end
def cache
@@ -101,14 +113,25 @@ class Chef
end
def cookbook_segment(cookbook_name, segment)
- @cookbooks_by_name[cookbook_name].manifest[segment]
+ @cookbooks_by_name[cookbook_name].files_for(segment)
end
def files
+ lazy = unless Chef::Config[:no_lazy_load]
+ %w{ files templates }
+ else
+ []
+ end
+
@files ||= cookbooks.inject([]) do |memo, cookbook|
- @eager_segments.each do |segment|
- cookbook.manifest[segment].each do |manifest_record|
- memo << CookbookFile.new(cookbook, segment, manifest_record)
+ cookbook.each_file do |manifest_record|
+ part = manifest_record[:name].split("/")[0]
+ if lazy.include?(part)
+ manifest_record[:lazy] = true
+ @lazy_files[cookbook] ||= []
+ @lazy_files[cookbook] << manifest_record
+ else
+ memo << CookbookFile.new(cookbook, manifest_record)
end
end
memo
@@ -116,7 +139,7 @@ class Chef
end
def files_by_cookbook
- files.group_by { |file| file.cookbook }
+ files.group_by(&:cookbook)
end
def files_remaining_by_cookbook
@@ -137,17 +160,18 @@ class Chef
end
# Synchronizes all the cookbooks from the chef-server.
- #)
+ # )
# === Returns
# true:: Always returns true
def sync_cookbooks
- Chef::Log.info("Loading cookbooks [#{cookbooks.map { |ckbk| ckbk.name + '@' + ckbk.version }.join(', ')}]")
- Chef::Log.debug("Cookbooks detail: #{cookbooks.inspect}")
+ Chef::Log.info("Loading cookbooks [#{cookbooks.map { |ckbk| ckbk.name + "@" + ckbk.version }.join(", ")}]")
+ Chef::Log.trace("Cookbooks detail: #{cookbooks.inspect}")
clear_obsoleted_cookbooks
queue = Chef::Util::ThreadedJobQueue.new
+ Chef::Log.warn("skipping cookbook synchronization! DO NOT LEAVE THIS ENABLED IN PRODUCTION!!!") if Chef::Config[:skip_cookbook_sync]
files.each do |file|
queue << lambda do |lock|
full_file_path = sync_file(file)
@@ -162,8 +186,10 @@ class Chef
@events.cookbook_sync_start(cookbook_count)
queue.process(Chef::Config[:cookbook_sync_threads])
+ # Ensure that cookbooks know where they're rooted at, for manifest purposes.
+ ensure_cookbook_paths
# Update the full file paths in the manifest
- update_cookbook_filenames()
+ update_cookbook_filenames
rescue Exception => e
@events.cookbook_sync_failed(cookbooks, e)
@@ -176,16 +202,15 @@ class Chef
# Saves the full_path to the file of the cookbook to be updated
# in the manifest later
def save_full_file_path(file, full_path)
- @cookbook_full_file_paths[file.cookbook] ||= {}
- @cookbook_full_file_paths[file.cookbook][file.segment] ||= [ ]
- @cookbook_full_file_paths[file.cookbook][file.segment] << full_path
+ @cookbook_full_file_paths[file.cookbook] ||= []
+ @cookbook_full_file_paths[file.cookbook] << full_path
end
# remove cookbooks that are not referenced in the expanded run_list at all
# (if we have an override run_list we may not want to do this)
def remove_old_cookbooks
cache.find(File.join(%w{cookbooks ** {*,.*}})).each do |cache_file|
- cache_file =~ /^cookbooks\/([^\/]+)\//
+ cache_file =~ %r{^cookbooks/([^/]+)/}
unless have_cookbook?($1)
Chef::Log.info("Removing #{cache_file} from the cache; its cookbook is no longer needed on this client.")
cache.delete(cache_file)
@@ -197,8 +222,9 @@ class Chef
# remove deleted files in cookbooks that are being used on the node
def remove_deleted_files
cache.find(File.join(%w{cookbooks ** {*,.*}})).each do |cache_file|
- md = cache_file.match(/^cookbooks\/([^\/]+)\/([^\/]+)\/(.*)/)
+ md = cache_file.match(%r{^cookbooks/([^/]+)/([^/]+)/(.*)})
next unless md
+
( cookbook_name, segment, file ) = md[1..3]
if have_cookbook?(cookbook_name)
manifest_segment = cookbook_segment(cookbook_name, segment)
@@ -229,10 +255,19 @@ class Chef
end
def update_cookbook_filenames
- @cookbook_full_file_paths.each do |cookbook, file_segments|
- file_segments.each do |segment, full_paths|
- cookbook.replace_segment_filenames(segment, full_paths)
- end
+ @cookbook_full_file_paths.each do |cookbook, full_paths|
+ cookbook.all_files = full_paths
+ end
+
+ @lazy_files.each do |cookbook, lazy_files|
+ cookbook.cookbook_manifest.add_files_to_manifest(lazy_files)
+ end
+ end
+
+ def ensure_cookbook_paths
+ cookbooks.each do |cookbook|
+ cb_dir = File.join(Chef::Config[:file_cache_path], "cookbooks", cookbook.name)
+ cookbook.root_paths = Array(cb_dir)
end
end
@@ -255,7 +290,7 @@ class Chef
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.")
+ Chef::Log.trace("Not storing #{cache_filename}, as the cache is up to date.")
end
# Load the file in the cache and return the full file path to the loaded file
@@ -263,11 +298,9 @@ class Chef
end
def cached_copy_up_to_date?(local_path, expected_checksum)
- if Chef::Config[:skip_cookbook_sync]
- Chef::Log.warn "skipping cookbook synchronization! DO NOT LEAVE THIS ENABLED IN PRODUCTION!!!"
- return true
- end
- if cache.has_key?(local_path)
+ return true if Chef::Config[:skip_cookbook_sync]
+
+ if cache.key?(local_path)
current_checksum = CookbookVersion.checksum_cookbook_file(cache.load(local_path, false))
expected_checksum == current_checksum
else
diff --git a/lib/chef/cookbook/syntax_check.rb b/lib/chef/cookbook/syntax_check.rb
index f8559433dc..8b593eea81 100644
--- a/lib/chef/cookbook/syntax_check.rb
+++ b/lib/chef/cookbook/syntax_check.rb
@@ -1,6 +1,5 @@
-#
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,12 +15,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" unless defined?(Pathname)
+require "stringio" unless defined?(StringIO)
+require "erubis" unless defined?(Erubis)
+require_relative "../mixin/shell_out"
+require_relative "../mixin/checksum"
+require_relative "../util/path_helper"
class Chef
class Cookbook
@@ -63,6 +62,7 @@ class Chef
def ensure_cache_path_created
return true if @cache_path_created
+
FileUtils.mkdir_p(cache_path)
@cache_path_created = true
end
@@ -84,10 +84,13 @@ class Chef
# If no +cookbook_path+ is given, +Chef::Config.cookbook_path+ is used.
def self.for_cookbook(cookbook_name, cookbook_path = nil)
cookbook_path ||= Chef::Config.cookbook_path
- unless cookbook_path
- raise ArgumentError, "Cannot find cookbook #{cookbook_name} unless Chef::Config.cookbook_path is set or an explicit cookbook path is given"
+
+ Array(cookbook_path).each do |entry|
+ path = File.expand_path(File.join(entry, cookbook_name.to_s))
+ return new(path) if Dir.exist?(path)
end
- new(File.join(cookbook_path, cookbook_name.to_s))
+
+ raise ArgumentError, "Cannot find cookbook #{cookbook_name} unless Chef::Config.cookbook_path is set or an explicit cookbook path is given"
end
# Create a new SyntaxCheck object
@@ -110,21 +113,20 @@ class Chef
end
def remove_uninteresting_ruby_files(file_list)
- file_list.reject { |f| f =~ %r{#{cookbook_path}/(files|templates)/} }
+ file_list.reject { |f| f =~ %r{#{Regexp.quote(cookbook_path)}/(files|templates)/} }
end
def ruby_files
path = Chef::Util::PathHelper.escape_glob_dir(cookbook_path)
files = Dir[File.join(path, "**", "*.rb")]
files = remove_ignored_files(files)
- files = remove_uninteresting_ruby_files(files)
- files
+ remove_uninteresting_ruby_files(files)
end
def untested_ruby_files
ruby_files.reject do |file|
if validated?(file)
- Chef::Log.debug("Ruby file #{file} is unchanged, skipping syntax check")
+ Chef::Log.trace("Ruby file #{file} is unchanged, skipping syntax check")
true
else
false
@@ -139,7 +141,7 @@ class Chef
def untested_template_files
template_files.reject do |file|
if validated?(file)
- Chef::Log.debug("Template #{file} is unchanged, skipping syntax check")
+ Chef::Log.trace("Template #{file} is unchanged, skipping syntax check")
true
else
false
@@ -158,6 +160,7 @@ class Chef
def validate_ruby_files
untested_ruby_files.each do |ruby_file|
return false unless validate_ruby_file(ruby_file)
+
validated(ruby_file)
end
end
@@ -165,17 +168,18 @@ class Chef
def validate_templates
untested_template_files.each do |template|
return false unless validate_template(template)
+
validated(template)
end
end
def validate_template(erb_file)
- Chef::Log.debug("Testing template #{erb_file} for syntax errors...")
+ Chef::Log.trace("Testing template #{erb_file} for syntax errors...")
validate_erb_file_inline(erb_file)
end
def validate_ruby_file(ruby_file)
- Chef::Log.debug("Testing #{ruby_file} for syntax errors...")
+ Chef::Log.trace("Testing #{ruby_file} for syntax errors...")
validate_ruby_file_inline(ruby_file)
end
diff --git a/lib/chef/cookbook_loader.rb b/lib/chef/cookbook_loader.rb
index bff77fa13b..f7efb2646e 100644
--- a/lib/chef/cookbook_loader.rb
+++ b/lib/chef/cookbook_loader.rb
@@ -2,7 +2,7 @@
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
# Author:: Daniel DeLeo (<dan@kallistec.com>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# Copyright:: Copyright 2009-2016, Daniel DeLeo
# License:: Apache License, Version 2.0
#
@@ -18,114 +18,90 @@
# 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_relative "config"
+require_relative "exceptions"
+require_relative "cookbook/cookbook_version_loader"
+require_relative "cookbook_version"
+require_relative "cookbook/chefignore"
+require_relative "cookbook/metadata"
-#
-# CookbookLoader class loads the cookbooks lazily as read
-#
class Chef
+ # This class is used by knife, cheffs and legacy chef-solo modes. It is not used by the server mode
+ # of chef-client or zolo/zero modes.
+ #
+ # This class implements orchestration around producing a single cookbook_version for a cookbook or
+ # loading a Mash of all cookbook_versions, using the cookbook_version_loader class, and doing
+ # lazy-access and memoization to only load each cookbook once on demand.
+ #
+ # This implements a key-value style each which makes it appear to be a Hash of String => CookbookVersion
+ # pairs where the String is the cookbook name. The use of Enumerable combined with the Hash-style
+ # each is likely not entirely sane.
+ #
+ # This object is also passed and injected into the CookbookCollection object where it is converted
+ # to a Mash that looks almost exactly like the cookbook_by_name Mash in this object.
+ #
class CookbookLoader
+ # @return [Array<String>] the array of repo paths containing cookbook dirs
+ attr_reader :repo_paths
- attr_reader :cookbooks_by_name
- attr_reader :merged_cookbooks
- attr_reader :cookbook_paths
- attr_reader :metadata
-
+ # XXX: this is highly questionable combined with the Hash-style each method
include Enumerable
+ # @param repo_paths [Array<String>] the array of repo paths containing cookbook dirs
def initialize(*repo_paths)
- repo_paths = repo_paths.flatten
- raise ArgumentError, "You must specify at least one cookbook repo path" if repo_paths.empty?
- @cookbooks_by_name = Mash.new
- @loaded_cookbooks = {}
- @metadata = Mash.new
- @cookbooks_paths = Hash.new { |h, k| h[k] = [] } # for deprecation warnings
- @chefignores = {}
- @repo_paths = repo_paths.map do |repo_path|
- File.expand_path(repo_path)
- end
-
- @preloaded_cookbooks = false
- @loaders_by_name = {}
-
- # Used to track which cookbooks appear in multiple places in the cookbook repos
- # and are merged in to a single cookbook by file shadowing. This behavior is
- # deprecated, so users of this class may issue warnings to the user by checking
- # this variable
- @merged_cookbooks = []
+ @repo_paths = repo_paths.flatten.compact.map { |p| File.expand_path(p) }
+ raise ArgumentError, "You must specify at least one cookbook repo path" if @repo_paths.empty?
end
- def merged_cookbook_paths # for deprecation warnings
- merged_cookbook_paths = {}
- @merged_cookbooks.each { |c| merged_cookbook_paths[c] = @cookbooks_paths[c] }
- merged_cookbook_paths
+ # The primary function of this class is to build this Mash mapping cookbook names as a string to
+ # the CookbookVersion objects for them. Callers must call "load_cookbooks" first.
+ #
+ # @return [Mash<String, Chef::CookbookVersion>]
+ def cookbooks_by_name
+ @cookbooks_by_name ||= Mash.new
end
- def warn_about_cookbook_shadowing
- unless merged_cookbooks.empty?
- Chef::Log.deprecation "The cookbook(s): #{merged_cookbooks.join(', ')} exist in multiple places in your cookbook_path. " +
- "A composite version has been compiled. This has been deprecated since 0.10.4, in Chef 13 this behavior will be REMOVED."
- end
+ # This class also builds a mapping of cookbook names to their Metadata objects. Callers must call
+ # "load_cookbooks" first.
+ #
+ # @return [Mash<String, Chef::Cookbook::Metadata>]
+ def metadata
+ @metadata ||= Mash.new
end
- # Will be removed when cookbook shadowing is removed, do NOT create new consumers of this API.
+ # Loads all cookbooks across all repo_paths
#
- # @api private
- def load_cookbooks_without_shadow_warning
- preload_cookbooks
- @loaders_by_name.each do |cookbook_name, _loaders|
+ # @return [Mash<String, Chef::CookbookVersion>] the cookbooks_by_name Mash
+ def load_cookbooks
+ cookbook_version_loaders.each_key do |cookbook_name|
load_cookbook(cookbook_name)
end
- @cookbooks_by_name
- end
-
- def load_cookbooks
- ret = load_cookbooks_without_shadow_warning
- warn_about_cookbook_shadowing
- ret
+ cookbooks_by_name
end
+ # Loads a single cookbook by its name.
+ #
+ # @param [String]
+ # @return [Chef::CookbookVersion]
def load_cookbook(cookbook_name)
- preload_cookbooks
-
- return @cookbooks_by_name[cookbook_name] if @cookbooks_by_name.has_key?(cookbook_name)
-
- return nil unless @loaders_by_name.key?(cookbook_name.to_s)
-
- cookbook_loaders_for(cookbook_name).each do |loader|
- loader.load
+ unless cookbook_version_loaders.key?(cookbook_name)
+ raise Exceptions::CookbookNotFoundInRepo, "Cannot find a cookbook named #{cookbook_name}; did you forget to add metadata to a cookbook? (https://docs.chef.io/config_rb_metadata/)"
+ end
- next if loader.empty?
+ return cookbooks_by_name[cookbook_name] if cookbooks_by_name.key?(cookbook_name)
- @cookbooks_paths[cookbook_name] << loader.cookbook_path # for deprecation warnings
+ loader = cookbook_version_loaders[cookbook_name]
- if @loaded_cookbooks.key?(cookbook_name)
- @merged_cookbooks << cookbook_name # for deprecation warnings
- @loaded_cookbooks[cookbook_name].merge!(loader)
- else
- @loaded_cookbooks[cookbook_name] = loader
- end
- end
+ loader.load!
- if @loaded_cookbooks.has_key?(cookbook_name)
- cookbook_version = @loaded_cookbooks[cookbook_name].cookbook_version
- @cookbooks_by_name[cookbook_name] = cookbook_version
- @metadata[cookbook_name] = cookbook_version.metadata
- end
- @cookbooks_by_name[cookbook_name]
+ cookbook_version = loader.cookbook_version
+ cookbooks_by_name[cookbook_name] = cookbook_version
+ metadata[cookbook_name] = cookbook_version.metadata unless cookbook_version.nil?
+ cookbook_version
end
def [](cookbook)
- if @cookbooks_by_name.has_key?(cookbook.to_sym) || load_cookbook(cookbook.to_sym)
- @cookbooks_by_name[cookbook.to_sym]
- else
- 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
+ load_cookbook(cookbook)
end
alias :fetch :[]
@@ -133,41 +109,83 @@ class Chef
def has_key?(cookbook_name)
not self[cookbook_name.to_sym].nil?
end
+
alias :cookbook_exists? :has_key?
alias :key? :has_key?
def each
- @cookbooks_by_name.keys.sort { |a, b| a.to_s <=> b.to_s }.each do |cname|
- yield(cname, @cookbooks_by_name[cname])
+ cookbooks_by_name.keys.sort_by(&:to_s).each do |cname|
+ yield(cname, cookbooks_by_name[cname])
end
end
+ def each_key(&block)
+ cookbook_names.each(&block)
+ end
+
+ def each_value(&block)
+ values.each(&block)
+ end
+
def cookbook_names
- @cookbooks_by_name.keys.sort
+ cookbooks_by_name.keys.sort
end
def values
- @cookbooks_by_name.values
+ cookbooks_by_name.values
+ end
+
+ # This method creates tmp directory and copies all cookbooks into it and creates cookbook loader object which points to tmp directory
+ def self.copy_to_tmp_dir_from_array(cookbooks)
+ Dir.mktmpdir do |tmp_dir|
+ cookbooks.each do |cookbook|
+ checksums_to_on_disk_paths = cookbook.checksums
+ cookbook.each_file do |manifest_record|
+ path_in_cookbook = manifest_record[:path]
+ on_disk_path = checksums_to_on_disk_paths[manifest_record[:checksum]]
+ dest = File.join(tmp_dir, cookbook.name.to_s, path_in_cookbook)
+ FileUtils.mkdir_p(File.dirname(dest))
+ FileUtils.cp_r(on_disk_path, dest)
+ end
+ end
+ tmp_cookbook_loader ||= begin
+ Chef::Cookbook::FileVendor.fetch_from_disk(tmp_dir)
+ CookbookLoader.new(tmp_dir)
+ end
+ yield tmp_cookbook_loader
+ end
end
- alias :cookbooks :values
- private
-
- def preload_cookbooks
- return false if @preloaded_cookbooks
+ # generates metadata.json adds it in the manifest
+ def compile_metadata
+ each do |cookbook_name, cookbook|
+ compiled_metadata = cookbook.compile_metadata
+ if compiled_metadata
+ cookbook.all_files << compiled_metadata
+ cookbook.cookbook_manifest.send(:generate_manifest)
+ end
+ end
+ end
- all_directories_in_repo_paths.each do |cookbook_path|
- preload_cookbook(cookbook_path)
+ # freeze versions of all the cookbooks
+ def freeze_versions
+ each do |cookbook_name, cookbook|
+ cookbook.freeze_version
end
- @preloaded_cookbooks = true
- true
end
- def preload_cookbook(cookbook_path)
- repo_path = File.dirname(cookbook_path)
+ alias :cookbooks :values
+
+ private
+
+ # Helper method to lazily create and remember the chefignore object
+ # for a given repo_path.
+ #
+ # @param [String] repo_path the full path to the cookbook directory of the repo
+ # @return [Chef::Cookbook::Chefignore] the chefignore object for the repo_path
+ def chefignore(repo_path)
+ @chefignores ||= {}
@chefignores[repo_path] ||= Cookbook::Chefignore.new(repo_path)
- loader = Cookbook::CookbookVersionLoader.new(cookbook_path, @chefignores[repo_path])
- add_cookbook_loader(loader)
end
def all_directories_in_repo_paths
@@ -178,23 +196,30 @@ class Chef
def all_files_in_repo_paths
@all_files_in_repo_paths ||=
begin
- @repo_paths.inject([]) do |all_children, repo_path|
+ repo_paths.inject([]) do |all_children, repo_path|
all_children + Dir[File.join(Chef::Util::PathHelper.escape_glob_dir(repo_path), "*")]
end
end
end
- def add_cookbook_loader(loader)
- cookbook_name = loader.cookbook_name
-
- @loaders_by_name[cookbook_name.to_s] ||= []
- @loaders_by_name[cookbook_name.to_s] << loader
- loader
- end
-
- def cookbook_loaders_for(cookbook_name)
- @loaders_by_name[cookbook_name.to_s]
+ # This method creates a Mash of the CookbookVersionLoaders for each cookbook.
+ #
+ # @return [Mash<String, Cookbook::CookbookVersionLoader>]
+ def cookbook_version_loaders
+ @cookbook_version_loaders ||=
+ begin
+ mash = Mash.new
+ all_directories_in_repo_paths.each do |cookbook_path|
+ loader = Cookbook::CookbookVersionLoader.new(cookbook_path, chefignore(cookbook_path))
+ cookbook_name = loader.cookbook_name
+ if mash.key?(cookbook_name)
+ raise Chef::Exceptions::CookbookMergingError, "Cookbook merging is no longer supported, the cookbook named #{cookbook_name} can only appear once in the cookbook_path"
+ end
+
+ mash[cookbook_name] = loader
+ end
+ mash
+ end
end
-
end
end
diff --git a/lib/chef/cookbook_manifest.rb b/lib/chef/cookbook_manifest.rb
index d6de9dd029..84b0c0d58c 100644
--- a/lib/chef/cookbook_manifest.rb
+++ b/lib/chef/cookbook_manifest.rb
@@ -1,5 +1,5 @@
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,9 +14,12 @@
# 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" unless defined?(Forwardable)
+require_relative "mixin/versioned_api"
+require_relative "util/path_helper"
+require_relative "cookbook/manifest_v0"
+require_relative "cookbook/manifest_v2"
+require_relative "log"
class Chef
@@ -24,17 +27,11 @@ class Chef
# to a Chef Server.
class CookbookManifest
- # Duplicates the same constant in CookbookVersion. We cannot remove it
- # there because it is treated by other code as part of CookbookVersion's
- # public API (also used in some deprecated methods).
- COOKBOOK_SEGMENTS = [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ].freeze
-
extend Forwardable
attr_reader :cookbook_version
def_delegator :@cookbook_version, :root_paths
- def_delegator :@cookbook_version, :segment_filenames
def_delegator :@cookbook_version, :name
def_delegator :@cookbook_version, :identifier
def_delegator :@cookbook_version, :metadata
@@ -43,11 +40,11 @@ class Chef
def_delegator :@cookbook_version, :frozen_version?
# Create a new CookbookManifest object for the given `cookbook_version`.
- # You can subsequently call #to_hash to get a Hash representation of the
+ # You can subsequently call #to_h to get a Hash representation of the
# cookbook_version in the "manifest" format, or #to_json to get a JSON
# representation of the cookbook_version.
#
- # The inferface for this behavior is expected to change as we implement new
+ # The interface for this behavior is expected to change as we implement new
# manifest formats. The entire class should be considered a private API for
# now.
#
@@ -56,10 +53,7 @@ class Chef
# the format used by the `cookbook_artifacts` endpoint (for policyfiles).
# Setting this option also changes the behavior of #save_url and
# #force_save_url such that CookbookVersions will be uploaded to the new
- # `cookbook_artifacts` API. This endpoint is currently under active
- # development and the format is expected to change frequently, therefore
- # the result of #manifest, #to_hash, and #to_json will not be stable when
- # `policy_mode` is enabled.
+ # `cookbook_artifacts` API.
def initialize(cookbook_version, policy_mode: false)
@cookbook_version = cookbook_version
@policy_mode = !!policy_mode
@@ -125,15 +119,14 @@ class Chef
@policy_mode
end
- def to_hash
- result = manifest.dup
- result["frozen?"] = frozen_version?
- result["chef_type"] = "cookbook_version"
- result.to_hash
+ def to_h
+ CookbookManifestVersions.to_h(self)
end
+ alias_method :to_hash, :to_h
+
def to_json(*a)
- result = to_hash
+ result = to_h
result["json_class"] = "Chef::CookbookVersion"
Chef::JSONCompat.to_json(result, *a)
end
@@ -164,15 +157,59 @@ class Chef
# make the corresponding changes to the cookbook_version object. Required
# to provide backward compatibility with CookbookVersion#manifest= method.
def update_from(new_manifest)
- @manifest = Mash.new new_manifest
+ @manifest = Chef::CookbookManifestVersions.from_hash(new_manifest)
+ @checksums = extract_checksums_from_manifest(@manifest)
+ @manifest_records_by_path = extract_manifest_records_by_path(@manifest)
+ end
+
+ # @api private
+ # takes a list of hashes
+ def add_files_to_manifest(files)
+ manifest[:all_files].concat(Array(files))
@checksums = extract_checksums_from_manifest(@manifest)
@manifest_records_by_path = extract_manifest_records_by_path(@manifest)
+ end
- COOKBOOK_SEGMENTS.each do |segment|
- next unless @manifest.has_key?(segment)
- filenames = @manifest[segment].map { |manifest_record| manifest_record["name"] }
+ def files_for(part)
+ return root_files if part.to_s == "root_files"
- cookbook_version.replace_segment_filenames(segment, filenames)
+ manifest[:all_files].select do |file|
+ seg = file[:name].split("/")[0]
+ part.to_s == seg
+ end
+ end
+
+ def each_file(excluded_parts: [], &block)
+ excluded_parts = Array(excluded_parts).map(&:to_s)
+
+ manifest[:all_files].each do |file|
+ seg = file[:name].split("/")[0]
+ next if excluded_parts.include?(seg)
+
+ yield file if block_given?
+ end
+ end
+
+ def by_parent_directory
+ @by_parent_directory ||=
+ manifest[:all_files].inject({}) do |memo, file|
+ parts = file[:name].split("/")
+ parent = if parts.length == 1
+ "root_files"
+ else
+ parts[0]
+ end
+
+ memo[parent] ||= []
+ memo[parent] << file
+ memo
+ end
+ end
+
+ def root_files
+ manifest[:all_files].select do |file|
+ segment, name = file[:name].split("/")
+ name.nil? || segment == "root_files"
end
end
@@ -186,15 +223,7 @@ class Chef
# See #preferred_manifest_record for a description an individual manifest record.
def generate_manifest
manifest = Mash.new({
- :recipes => Array.new,
- :definitions => Array.new,
- :libraries => Array.new,
- :attributes => Array.new,
- :files => Array.new,
- :templates => Array.new,
- :resources => Array.new,
- :providers => Array.new,
- :root_files => Array.new,
+ all_files: [],
})
@checksums = {}
@@ -203,24 +232,24 @@ class Chef
raise "Cookbook #{name} does not have root_paths! Cannot generate manifest."
end
- COOKBOOK_SEGMENTS.each do |segment|
- segment_filenames(segment).each do |segment_file|
- next if File.directory?(segment_file)
+ @cookbook_version.all_files.each do |file|
+ next if File.directory?(file)
- path, specificity = parse_segment_file_from_root_paths(segment, segment_file)
- file_name = File.basename(path)
+ name, path, specificity = parse_file_from_root_paths(file)
- csum = checksum_cookbook_file(segment_file)
- @checksums[csum] = segment_file
- rs = Mash.new({
- :name => file_name,
- :path => path,
- :checksum => csum,
- :specificity => specificity,
- })
+ csum = checksum_cookbook_file(file)
+ @checksums[csum] = file
+ rs = Mash.new({
+ name: name,
+ path: path,
+ checksum: csum,
+ specificity: specificity,
+ # full_path is not a part of the normal manifest, but is very useful to keep around.
+ # uploaders should strip this out.
+ full_path: file,
+ })
- manifest[segment] << rs
- end
+ manifest[:all_files] << rs
end
manifest[:metadata] = metadata
@@ -238,38 +267,42 @@ class Chef
@manifest = manifest
end
- def parse_segment_file_from_root_paths(segment, segment_file)
+ def parse_file_from_root_paths(file)
root_paths.each do |root_path|
- pathname = Chef::Util::PathHelper.relative_path_from(root_path, segment_file)
+ pathname = Chef::Util::PathHelper.relative_path_from(root_path, file)
parts = pathname.each_filename.take(2)
# Check if path is actually under root_path
next if parts[0] == ".."
- if segment == :templates || segment == :files
+
+ # if we have a root_file, such as metadata.rb, the first part will be "."
+ return [ "root_files/#{pathname}", pathname.to_s, "default" ] if parts.length == 1
+
+ segment = parts[0]
+
+ name = File.join(segment, pathname.basename.to_s)
+
+ if %w{templates files}.include?(segment)
# 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 [ name, pathname.to_s, "root_default" ]
else
- return [ pathname.to_s, parts[1] ]
+ return [ name, pathname.to_s, parts[1] ]
end
else
- return [ pathname.to_s, "default" ]
+ return [ name, pathname.to_s, "default" ]
end
end
- Chef::Log.error("Cookbook file #{segment_file} not under cookbook root paths #{root_paths.inspect}.")
- raise "Cookbook file #{segment_file} not under cookbook root paths #{root_paths.inspect}."
+ Chef::Log.error("Cookbook file #{file} not under cookbook root paths #{root_paths.inspect}.")
+ raise "Cookbook file #{file} not under cookbook root paths #{root_paths.inspect}."
end
def extract_checksums_from_manifest(manifest)
- checksums = {}
- COOKBOOK_SEGMENTS.each do |segment|
- next unless manifest.has_key?(segment)
- manifest[segment].each do |manifest_record|
- checksums[manifest_record[:checksum]] = nil
- end
+ manifest[:all_files].inject({}) do |memo, manifest_record|
+ memo[manifest_record[:checksum]] = nil
+ memo
end
- checksums
end
def checksum_cookbook_file(filepath)
@@ -277,14 +310,22 @@ class Chef
end
def extract_manifest_records_by_path(manifest)
- manifest_records_by_path = {}
- COOKBOOK_SEGMENTS.each do |segment|
- next unless manifest.has_key?(segment)
- manifest[segment].each do |manifest_record|
- manifest_records_by_path[manifest_record[:path]] = manifest_record
- end
+ manifest[:all_files].inject({}) do |memo, manifest_record|
+ memo[manifest_record[:path]] = manifest_record
+ memo
end
- manifest_records_by_path
end
+
+ end
+
+ class CookbookManifestVersions
+
+ extend Chef::Mixin::VersionedAPIFactory
+ add_versioned_api_class Chef::Cookbook::ManifestV0
+ add_versioned_api_class Chef::Cookbook::ManifestV2
+
+ def_versioned_delegator :from_hash
+ def_versioned_delegator :to_hash
+ def_versioned_delegator :to_h
end
end
diff --git a/lib/chef/cookbook_site_streaming_uploader.rb b/lib/chef/cookbook_site_streaming_uploader.rb
index c0e85ff984..d7226b79b3 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@chef.io)
# Author:: Christopher Walters (<cw@chef.io>)
-# Copyright:: Copyright 2009-2016, 2010-2016 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,10 +18,17 @@
# limitations under the License.
#
-require "uri"
-require "net/http"
-require "mixlib/authentication/signedheaderauth"
-require "openssl"
+autoload :URI, "uri"
+module Net
+ autoload :HTTP, "net/http"
+end
+autoload :OpenSSL, "openssl"
+module Mixlib
+ module Authentication
+ autoload :SignedHeaderAuth, "mixlib/authentication/signedheaderauth"
+ end
+end
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
# == Chef::CookbookSiteStreamingUploader
@@ -31,31 +38,29 @@ class Chef
# inspired by http://stanislavvitvitskiy.blogspot.com/2008/12/multipart-post-in-ruby.html
class CookbookSiteStreamingUploader
- DefaultHeaders = { "accept" => "application/json", "x-chef-version" => ::Chef::VERSION } # rubocop:disable Style/ConstantName
+ DefaultHeaders = { "accept" => "application/json", "x-chef-version" => ::Chef::VERSION }.freeze # rubocop:disable Naming/ConstantName
class << self
def create_build_dir(cookbook)
- tmp_cookbook_path = Tempfile.new("chef-#{cookbook.name}-build")
+ tmp_cookbook_path = Tempfile.new("#{ChefUtils::Dist::Infra::SHORT}-#{cookbook.name}-build")
tmp_cookbook_path.close
tmp_cookbook_dir = tmp_cookbook_path.path
File.unlink(tmp_cookbook_dir)
FileUtils.mkdir_p(tmp_cookbook_dir)
- Chef::Log.debug("Staging at #{tmp_cookbook_dir}")
+ Chef::Log.trace("Staging at #{tmp_cookbook_dir}")
checksums_to_on_disk_paths = cookbook.checksums
- Chef::CookbookVersion::COOKBOOK_SEGMENTS.each do |segment|
- cookbook.manifest[segment].each do |manifest_record|
- path_in_cookbook = manifest_record[:path]
- on_disk_path = checksums_to_on_disk_paths[manifest_record[:checksum]]
- dest = File.join(tmp_cookbook_dir, cookbook.name.to_s, path_in_cookbook)
- FileUtils.mkdir_p(File.dirname(dest))
- Chef::Log.debug("Staging #{on_disk_path} to #{dest}")
- FileUtils.cp(on_disk_path, dest)
- end
+ cookbook.each_file do |manifest_record|
+ path_in_cookbook = manifest_record[:path]
+ on_disk_path = checksums_to_on_disk_paths[manifest_record[:checksum]]
+ dest = File.join(tmp_cookbook_dir, cookbook.name.to_s, path_in_cookbook)
+ FileUtils.mkdir_p(File.dirname(dest))
+ Chef::Log.trace("Staging #{on_disk_path} to #{dest}")
+ FileUtils.cp(on_disk_path, dest)
end
# First, generate metadata
- Chef::Log.debug("Generating metadata")
+ Chef::Log.trace("Generating metadata")
kcm = Chef::Knife::CookbookMetadata.new
kcm.config[:cookbook_path] = [ tmp_cookbook_dir ]
kcm.name_args = [ cookbook.name.to_s ]
@@ -81,7 +86,7 @@ class Chef
unless params.nil? || params.empty?
params.each do |key, value|
- if value.kind_of?(File)
+ if value.is_a?(File)
content_file = value
filepath = value.path
filename = File.basename(filepath)
@@ -117,10 +122,10 @@ class Chef
content_file.rewind if content_file # we consumed the file for the above operation, so rewind it.
signing_options = {
- :http_method => http_verb,
- :path => url.path,
- :user_id => user_id,
- :timestamp => timestamp }
+ http_method: http_verb,
+ path: url.path,
+ user_id: user_id,
+ timestamp: timestamp }
(content_file && signing_options[:file] = content_file) || (signing_options[:body] = (content_body || ""))
headers.merge!(Mixlib::Authentication::SignedHeaderAuth.signing_object(signing_options).sign(secret_key))
@@ -148,7 +153,7 @@ class Chef
class << res
alias :to_s :body
- # BUGBUG this makes the response compatible with what respsonse_steps expects to test headers (response.headers[] -> response[])
+ # BUG this makes the response compatible with what response_steps expects to test headers (response.headers[] -> response[])
def headers # rubocop:disable Lint/NestedMethodDefinition
self
end
@@ -186,7 +191,7 @@ class Chef
@str.length
end
- # read the specified amount from the string startiung at the offset
+ # read the specified amount from the string starting at the offset
def read(offset, how_much)
@str[offset, how_much]
end
@@ -226,11 +231,7 @@ class Chef
@part_no += 1
@part_offset = 0
next_part = read(how_much_next_part)
- result = current_part + if next_part
- next_part
- else
- ""
- end
+ result = current_part + (next_part || "")
else
@part_offset += how_much_current_part
result = current_part
diff --git a/lib/chef/cookbook_uploader.rb b/lib/chef/cookbook_uploader.rb
index bb75234563..235a539b94 100644
--- a/lib/chef/cookbook_uploader.rb
+++ b/lib/chef/cookbook_uploader.rb
@@ -1,15 +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 "chef/server_api"
+autoload :Set, "set"
+require_relative "exceptions"
+require_relative "knife/cookbook_metadata"
+require_relative "digester"
+require_relative "cookbook_manifest"
+require_relative "cookbook_version"
+require_relative "cookbook/syntax_check"
+require_relative "cookbook/file_system_file_vendor"
+require_relative "util/threaded_job_queue"
+require_relative "sandbox"
+require_relative "server_api"
class Chef
class CookbookUploader
@@ -40,14 +40,14 @@ class Chef
def initialize(cookbooks, opts = {})
@opts = opts
@cookbooks = Array(cookbooks)
- @rest = opts[:rest] || Chef::ServerAPI.new(Chef::Config[:chef_server_url])
+ @rest = opts[:rest] || Chef::ServerAPI.new(Chef::Config[:chef_server_url], version_class: Chef::CookbookManifestVersions)
@concurrency = opts[:concurrency] || 10
@policy_mode = opts[:policy_mode] || false
end
def upload_cookbooks
# Syntax Check
- validate_cookbooks
+ validate_cookbooks unless opts[:skip_syntax_check]
# generate checksums of cookbook files and create a sandbox
checksum_files = {}
cookbooks.each do |cb|
@@ -56,7 +56,7 @@ class Chef
end
checksums = checksum_files.inject({}) { |memo, elt| memo[elt.first] = nil; memo }
- new_sandbox = rest.post("sandboxes", { :checksums => checksums })
+ new_sandbox = rest.post("sandboxes", { checksums: checksums })
Chef::Log.info("Uploading files")
@@ -68,23 +68,23 @@ class Chef
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']}")
+ 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)
else
- Chef::Log.debug("#{checksum_files[checksum]} has not changed")
+ Chef::Log.trace("#{checksum_files[checksum]} has not changed")
end
end
queue.process(@concurrency)
sandbox_url = new_sandbox["uri"]
- Chef::Log.debug("Committing sandbox")
+ Chef::Log.trace("Committing sandbox")
# Retry if S3 is claims a checksum doesn't exist (the eventual
# in eventual consistency)
retries = 0
begin
- rest.put(sandbox_url, { :is_completed => true })
- rescue Net::HTTPServerException => e
+ rest.put(sandbox_url, { is_completed: true })
+ rescue Net::HTTPClientException => e
if e.message =~ /^400/ && (retries += 1) <= 5
sleep 2
retry
@@ -101,7 +101,7 @@ class Chef
save_url = opts[:force] ? manifest.force_save_url : manifest.save_url
begin
rest.put(save_url, manifest)
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => e
case e.response.code
when "409"
raise Chef::Exceptions::CookbookFrozen, "Version #{cb.version} of cookbook #{cb.name} is frozen. Use --force to override."
@@ -120,7 +120,7 @@ class Chef
# but we need the base64 encoding for the content-md5
# header
checksum64 = Base64.encode64([checksum].pack("H*")).strip
- file_contents = File.open(file, "rb") { |f| f.read }
+ file_contents = File.open(file, "rb", &:read)
# Custom headers. 'content-type' disables JSON serialization of the request body.
headers = { "content-type" => "application/x-binary", "content-md5" => checksum64, "accept" => "application/json" }
@@ -128,7 +128,7 @@ class Chef
begin
rest.put(url, file_contents, headers)
checksums_to_upload.delete(checksum)
- rescue Net::HTTPServerException, Net::HTTPFatalError, Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, SocketError => e
+ rescue Net::HTTPClientException, Net::HTTPFatalError, Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, SocketError => e
error_message = "Failed to upload #{file} (#{checksum}) to #{url} : #{e.message}"
error_message << "\n#{e.response.body}" if e.respond_to?(:response)
Chef::Knife.ui.error(error_message)
@@ -139,13 +139,14 @@ class Chef
def validate_cookbooks
cookbooks.each do |cb|
- syntax_checker = Chef::Cookbook::SyntaxCheck.for_cookbook(cb.name)
+ next if cb.nil?
+
+ syntax_checker = Chef::Cookbook::SyntaxCheck.new(cb.root_dir)
Chef::Log.info("Validating ruby files")
exit(1) unless syntax_checker.validate_ruby_files
Chef::Log.info("Validating templates")
exit(1) unless syntax_checker.validate_templates
Chef::Log.info("Syntax OK")
- true
end
end
diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb
index 8de9cb26dd..420532585a 100644
--- a/lib/chef/cookbook_version.rb
+++ b/lib/chef/cookbook_version.rb
@@ -4,7 +4,7 @@
# Author:: Tim Hinderliter (<tim@chef.io>)
# Author:: Seth Falcon (<seth@chef.io>)
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,13 +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/server_api"
+require_relative "log"
+require_relative "cookbook/file_vendor"
+require_relative "cookbook/metadata"
+require_relative "version_class"
+require_relative "digester"
+require_relative "cookbook_manifest"
+require_relative "server_api"
class Chef
@@ -37,50 +37,23 @@ class Chef
class CookbookVersion
include Comparable
+ extend Forwardable
- COOKBOOK_SEGMENTS = [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ]
+ def_delegator :@cookbook_manifest, :files_for
+ def_delegator :@cookbook_manifest, :each_file
- attr_accessor :all_files
+ COOKBOOK_SEGMENTS = %i{resources providers recipes definitions libraries attributes files templates root_files}.freeze
+
+ attr_reader :all_files
attr_accessor :root_paths
- attr_accessor :definition_filenames
- attr_accessor :template_filenames
- attr_accessor :file_filenames
- attr_accessor :library_filenames
- attr_accessor :resource_filenames
- attr_accessor :provider_filenames
- attr_accessor :root_filenames
attr_accessor :name
- attr_accessor :metadata_filenames
-
- def status=(new_status)
- 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. This method will be removed.")
- @status
- end
# A Chef::Cookbook::Metadata object. It has a setter that fixes up the
# metadata to add descriptions of the recipes contained in this
# CookbookVersion.
attr_reader :metadata
- # attribute_filenames also has a setter that has non-default
- # functionality.
- attr_reader :attribute_filenames
-
- # recipe_filenames also has a setter that has non-default
- # functionality.
- attr_reader :recipe_filenames
-
- attr_reader :recipe_filenames_by_name
- attr_reader :attribute_filenames_by_short_filename
-
- attr_accessor :chef_server_rest
-
# The `identifier` field is used for cookbook_artifacts, which are
# organized on the chef server according to their content. If the
# policy_mode option to CookbookManifest is set to true it will include
@@ -96,12 +69,17 @@ class Chef
root_paths[0]
end
+ def all_files=(files)
+ @all_files = Array(files)
+ cookbook_manifest.reset!
+ end
+
# This is the one and only method that knows how cookbook files'
# checksums are generated.
def self.checksum_cookbook_file(filepath)
Chef::Digester.generate_md5_checksum_for_file(filepath)
rescue Errno::ENOENT
- Chef::Log.debug("File #{filepath} does not exist, so there is no checksum to generate")
+ Chef::Log.trace("File #{filepath} does not exist, so there is no checksum to generate")
nil
end
@@ -118,23 +96,10 @@ class Chef
@root_paths = root_paths
@frozen = false
- @attribute_filenames = Array.new
- @definition_filenames = Array.new
- @template_filenames = Array.new
- @file_filenames = Array.new
- @recipe_filenames = Array.new
- @recipe_filenames_by_name = Hash.new
- @library_filenames = Array.new
- @resource_filenames = Array.new
- @provider_filenames = Array.new
- @metadata_filenames = Array.new
- @root_filenames = Array.new
-
- @all_files = Array.new
-
- # deprecated
- @status = :ready
+ @all_files = []
+
@file_vendor = nil
+ @cookbook_manifest = Chef::CookbookManifest.new(self)
@metadata = Chef::Cookbook::Metadata.new
@chef_server_rest = chef_server_rest
end
@@ -143,9 +108,9 @@ class Chef
metadata.version
end
- # Indicates if this version is frozen or not. Freezing a coobkook version
+ # Indicates if this version is frozen or not. Freezing a cookbook version
# indicates that a new cookbook with the same name and version number
- # shoule
+ # should
def frozen_version?
@frozen
end
@@ -163,26 +128,52 @@ class Chef
"#{name}-#{version}"
end
- def attribute_filenames=(*filenames)
- @attribute_filenames = filenames.flatten
- @attribute_filenames_by_short_filename = filenames_by_name(attribute_filenames)
- attribute_filenames
+ def attribute_filenames_by_short_filename
+ @attribute_filenames_by_short_filename ||= begin
+ name_map = filenames_by_name(files_for("attributes"))
+ root_alias = cookbook_manifest.root_files.find { |record| record[:name] == "root_files/attributes.rb" }
+ name_map["default"] = root_alias[:full_path] if root_alias
+ name_map
+ end
+ end
+
+ def recipe_yml_filenames_by_name
+ @recipe_ym_filenames_by_name ||= begin
+ name_map = yml_filenames_by_name(files_for("recipes"))
+ root_alias = cookbook_manifest.root_files.find { |record| record[:name] == "root_files/recipe.yml" }
+ if root_alias
+ Chef::Log.error("Cookbook #{name} contains both recipe.yml and and recipes/default.yml, ignoring recipes/default.yml") if name_map["default"]
+ name_map["default"] = root_alias[:full_path]
+ end
+ name_map
+ end
+ end
+
+ def recipe_filenames_by_name
+ @recipe_filenames_by_name ||= begin
+ name_map = filenames_by_name(files_for("recipes"))
+ root_alias = cookbook_manifest.root_files.find { |record| record[:name] == "root_files/recipe.rb" }
+ if root_alias
+ Chef::Log.error("Cookbook #{name} contains both recipe.rb and and recipes/default.rb, ignoring recipes/default.rb") if name_map["default"]
+ name_map["default"] = root_alias[:full_path]
+ end
+ name_map
+ end
end
def metadata=(metadata)
@metadata = metadata
@metadata.recipes_from_cookbook_version(self)
- @metadata
end
- ## BACKCOMPAT/DEPRECATED - Remove these and fix breakage before release [DAN - 5/20/2010]##
- alias :attribute_files :attribute_filenames
- alias :attribute_files= :attribute_filenames=
-
def manifest
cookbook_manifest.manifest
end
+ def manifest=(new_manifest)
+ cookbook_manifest.update_from(new_manifest)
+ end
+
# Returns a hash of checksums to either nil or the on disk path (which is
# done by generate_manifest).
def checksums
@@ -193,83 +184,55 @@ class Chef
cookbook_manifest.manifest_records_by_path
end
- def manifest=(new_manifest)
- cookbook_manifest.update_from(new_manifest)
- end
-
# Return recipe names in the form of cookbook_name::recipe_name
def fully_qualified_recipe_names
- results = Array.new
- recipe_filenames_by_name.each_key do |rname|
- results << "#{name}::#{rname}"
+ files_for("recipes").inject([]) do |memo, recipe|
+ rname = recipe[:name].split("/")[1]
+ rname = File.basename(rname, ".rb")
+ memo << "#{name}::#{rname}"
+ memo
end
- results
- end
-
- def recipe_filenames=(*filenames)
- @recipe_filenames = filenames.flatten
- @recipe_filenames_by_name = filenames_by_name(recipe_filenames)
- recipe_filenames
end
- ## BACKCOMPAT/DEPRECATED - Remove these and fix breakage before release [DAN - 5/20/2010]##
- alias :recipe_files :recipe_filenames
- alias :recipe_files= :recipe_filenames=
-
# called from DSL
def load_recipe(recipe_name, run_context)
- unless recipe_filenames_by_name.has_key?(recipe_name)
+ if recipe_filenames_by_name.key?(recipe_name)
+ load_ruby_recipe(recipe_name, run_context)
+ elsif recipe_yml_filenames_by_name.key?(recipe_name)
+ load_yml_recipe(recipe_name, run_context)
+ else
raise Chef::Exceptions::RecipeNotFound, "could not find recipe #{recipe_name} for cookbook #{name}"
end
+ end
- Chef::Log.debug("Found recipe #{recipe_name} in cookbook #{name}")
+ def load_yml_recipe(recipe_name, run_context)
+ Chef::Log.trace("Found recipe #{recipe_name} in cookbook #{name}")
recipe = Chef::Recipe.new(name, recipe_name, run_context)
- recipe_filename = recipe_filenames_by_name[recipe_name]
+ recipe_filename = recipe_yml_filenames_by_name[recipe_name]
unless recipe_filename
raise Chef::Exceptions::RecipeNotFound, "could not find #{recipe_name} files for cookbook #{name}"
end
- recipe.from_file(recipe_filename)
+ recipe.from_yaml_file(recipe_filename)
recipe
end
- def segment_filenames(segment)
- unless COOKBOOK_SEGMENTS.include?(segment)
- raise ArgumentError, "invalid segment #{segment}: must be one of #{COOKBOOK_SEGMENTS.join(', ')}"
- end
+ def load_ruby_recipe(recipe_name, run_context)
+ Chef::Log.trace("Found recipe #{recipe_name} in cookbook #{name}")
+ recipe = Chef::Recipe.new(name, recipe_name, run_context)
+ recipe_filename = recipe_filenames_by_name[recipe_name]
- case segment.to_sym
- when :resources
- @resource_filenames
- when :providers
- @provider_filenames
- when :recipes
- @recipe_filenames
- when :libraries
- @library_filenames
- when :definitions
- @definition_filenames
- when :attributes
- @attribute_filenames
- when :files
- @file_filenames
- when :templates
- @template_filenames
- when :root_files
- @root_filenames
+ unless recipe_filename
+ raise Chef::Exceptions::RecipeNotFound, "could not find #{recipe_name} files for cookbook #{name}"
end
+
+ recipe.from_file(recipe_filename)
+ recipe
end
- def replace_segment_filenames(segment, filenames)
- case segment.to_sym
- when :recipes
- self.recipe_filenames = filenames
- when :attributes
- self.attribute_filenames = filenames
- else
- segment_filenames(segment).replace(filenames)
- end
+ def segment_filenames(segment)
+ files_for(segment).map { |f| f["full_path"] || File.join(root_dir, f["path"]) }
end
# Query whether a template file +template_filename+ is available. File
@@ -301,12 +264,13 @@ class Chef
if found_pref
manifest_records_by_path[found_pref]
else
- if segment == :files || segment == :templates
+ if %i{files templates}.include?(segment)
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}/host-#{node[:fqdn]}/#{filename}",
" #{segment}/#{node[:platform]}-#{node[:platform_version]}/#{filename}",
" #{segment}/#{node[:platform]}/#{filename}",
" #{segment}/default/#{filename}",
@@ -346,10 +310,10 @@ class Chef
def relative_filenames_in_preferred_directory(node, segment, dirname)
preferences = preferences_for_path(node, segment, dirname)
- filenames_by_pref = Hash.new
- preferences.each { |pref| filenames_by_pref[pref] = Array.new }
+ filenames_by_pref = {}
+ preferences.each { |pref| filenames_by_pref[pref] = [] }
- manifest[segment].each do |manifest_record|
+ files_for(segment).each do |manifest_record|
manifest_record_path = manifest_record[:path]
# find the NON SPECIFIC filenames, but prefer them by filespecificity.
@@ -363,9 +327,9 @@ class Chef
# we're just going to make cookbook_files out of these and make the
# 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)})\/.+$/
+ if manifest_record_path =~ %r{(#{Regexp.escape(segment.to_s)}/[^/]*/?#{Regexp.escape(dirname)})/.+$}
specificity_dirname = $1
- non_specific_path = manifest_record_path[/#{Regexp.escape(segment.to_s)}\/[^\/]+\/#{Regexp.escape(dirname)}\/(.+)$/, 1]
+ non_specific_path = manifest_record_path[%r{#{Regexp.escape(segment.to_s)}/[^/]*/?#{Regexp.escape(dirname)}/(.+)$}, 1]
# Record the specificity_dirname only if it's in the list of
# valid preferences
if filenames_by_pref[specificity_dirname]
@@ -386,18 +350,18 @@ class Chef
# description of entries of the returned Array.
def preferred_manifest_records_for_directory(node, segment, dirname)
preferences = preferences_for_path(node, segment, dirname)
- records_by_pref = Hash.new
- preferences.each { |pref| records_by_pref[pref] = Array.new }
+ records_by_pref = {}
+ preferences.each { |pref| records_by_pref[pref] = [] }
- manifest[segment].each do |manifest_record|
+ files_for(segment).each do |manifest_record|
manifest_record_path = manifest_record[:path]
# 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.
+ if manifest_record_path =~ %r{(#{Regexp.escape(segment.to_s)}/[^/]+/#{Regexp.escape(dirname)})/.+$}
+ # Note the specificity_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
@@ -428,7 +392,7 @@ class Chef
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)/
+ if /Cannot find a (?:platform|version)/.match?(e.message)
platform = "/unknown_platform/"
version = "/unknown_platform_version/"
else
@@ -469,12 +433,22 @@ class Chef
end
private :preferences_for_path
+ def display
+ output = Mash.new
+ output["cookbook_name"] = name
+ output["name"] = full_name
+ output["frozen?"] = frozen_version?
+ output["metadata"] = metadata.to_h
+ output["version"] = version
+ output.merge(cookbook_manifest.by_parent_directory)
+ end
+
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.metadata = Chef::Cookbook::Metadata.from_hash(o["metadata"])
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. <<, >>)
@@ -484,42 +458,10 @@ class Chef
cookbook_version
end
- def self.json_create(o)
- Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::CookbookVersion#from_hash")
- from_hash(o)
- end
-
def self.from_cb_artifact_data(o)
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
- Chef.log_deprecation("Deprecated method #generate_manifest_with_urls.")
-
- rendered_manifest = manifest.dup
- COOKBOOK_SEGMENTS.each do |segment|
- if rendered_manifest.has_key?(segment)
- rendered_manifest[segment].each do |manifest_record|
- url_options = { :cookbook_name => name.to_s, :cookbook_version => version, :checksum => manifest_record["checksum"] }
- manifest_record["url"] = yield(url_options)
- end
- end
- end
- rendered_manifest
- end
-
- def to_hash
- # TODO: this should become deprecated when the API for CookbookManifest becomes stable
- cookbook_manifest.to_hash
- end
-
- def to_json(*a)
- # TODO: this should become deprecated when the API for CookbookManifest becomes stable
- cookbook_manifest.to_json
- end
-
def metadata_json_file
File.join(root_paths[0], "metadata.json")
end
@@ -534,26 +476,20 @@ class Chef
end
end
+ def has_metadata_file?
+ all_files.include?(metadata_json_file) || all_files.include?(metadata_rb_file)
+ end
+
##
# REST API
##
- def save_url
- # TODO: this should become deprecated when the API for CookbookManifest becomes stable
- cookbook_manifest.save_url
- end
-
- def force_save_url
- # TODO: this should become deprecated when the API for CookbookManifest becomes stable
- cookbook_manifest.force_save_url
- end
-
def chef_server_rest
- @chef_server_rest ||= self.chef_server_rest
+ @chef_server_rest ||= chef_server_rest
end
def self.chef_server_rest
- Chef::ServerAPI.new(Chef::Config[:chef_server_url])
+ Chef::ServerAPI.new(Chef::Config[:chef_server_url], { version_class: Chef::CookbookManifestVersions })
end
def destroy
@@ -590,8 +526,8 @@ class Chef
chef_server_rest.get("cookbooks/#{cookbook_name}")[cookbook_name]["versions"].map do |cb|
cb["version"]
end
- rescue Net::HTTPServerException => e
- if e.to_s =~ /^404/
+ rescue Net::HTTPClientException => e
+ if /^404/.match?(e.to_s)
Chef::Log.error("Cannot find a cookbook named #{cookbook_name}")
nil
else
@@ -600,37 +536,58 @@ class Chef
end
def <=>(other)
- raise Chef::Exceptions::CookbookVersionNameMismatch if self.name != other.name
+ raise Chef::Exceptions::CookbookVersionNameMismatch if name != other.name
+
# FIXME: can we change the interface to the Metadata class such
# that metadata.version returns a Chef::Version instance instead
# of a string?
- Chef::Version.new(self.version) <=> Chef::Version.new(other.version)
+ Chef::Version.new(version) <=> Chef::Version.new(other.version)
end
- private
-
def cookbook_manifest
@cookbook_manifest ||= CookbookManifest.new(self)
end
+ def compile_metadata(path = root_dir)
+ json_file = "#{path}/metadata.json"
+ rb_file = "#{path}/metadata.rb"
+ return nil if File.exist?(json_file)
+
+ md = Chef::Cookbook::Metadata.new
+ md.from_file(rb_file)
+ f = File.open(json_file, "w")
+ f.write(Chef::JSONCompat.to_json_pretty(md))
+ f.close
+ f.path
+ end
+
+ private
+
def find_preferred_manifest_record(node, segment, filename)
preferences = preferences_for_path(node, segment, filename)
- # in order of prefernce, look for the filename in the manifest
+ # in order of preference, look for the filename in the manifest
preferences.find { |preferred_filename| manifest_records_by_path[preferred_filename] }
end
- # For each filename, produce a mapping of base filename (i.e. recipe name
+ # For each manifest record, produce a mapping of base filename (i.e. recipe name
+ # or attribute file) to on disk location
+ def relative_paths_by_name(records)
+ records.select { |record| record[:name] =~ /\.rb$/ }.inject({}) { |memo, record| memo[File.basename(record[:name], ".rb")] = record[:path]; memo }
+ end
+
+ # For each manifest record, 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 }
+ def filenames_by_name(records)
+ records.select { |record| record[:name] =~ /\.rb$/ }.inject({}) { |memo, record| memo[File.basename(record[:name], ".rb")] = record[:full_path]; memo }
+ end
+
+ def yml_filenames_by_name(records)
+ records.select { |record| record[:name] =~ /\.yml$/ }.inject({}) { |memo, record| memo[File.basename(record[:name], ".yml")] = record[:full_path]; memo }
end
def file_vendor
- unless @file_vendor
- @file_vendor = Chef::Cookbook::FileVendor.create_from_manifest(manifest)
- end
- @file_vendor
+ @file_vendor ||= Chef::Cookbook::FileVendor.create_from_manifest(cookbook_manifest)
end
end
diff --git a/lib/chef/daemon.rb b/lib/chef/daemon.rb
index 70bdddf457..41d93e1fe6 100644
--- a/lib/chef/daemon.rb
+++ b/lib/chef/daemon.rb
@@ -1,6 +1,6 @@
#
# Author:: AJ Christensen (<aj@junglist.gen.nz>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,9 +17,9 @@
# I love you Merb (lib/merb-core/server.rb)
-require "chef/config"
-require "chef/run_lock"
-require "etc"
+require_relative "config"
+require_relative "run_lock"
+require "etc" unless defined?(Etc)
class Chef
class Daemon
diff --git a/lib/chef/data_bag.rb b/lib/chef/data_bag.rb
index f107f98f28..af0560a640 100644
--- a/lib/chef/data_bag.rb
+++ b/lib/chef/data_bag.rb
@@ -2,7 +2,7 @@
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Nuo Yan (<nuo@chef.io>)
# Author:: Christopher Brown (<cb@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,13 +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/server_api"
+require_relative "config"
+require_relative "mixin/params_validate"
+require_relative "mixin/from_file"
+require_relative "data_bag_item"
+require_relative "mash"
+require_relative "json_compat"
+require_relative "server_api"
class Chef
class DataBag
@@ -32,14 +32,16 @@ class Chef
include Chef::Mixin::FromFile
include Chef::Mixin::ParamsValidate
- VALID_NAME = /^[\.\-[:alnum:]_]+$/
-
- attr_accessor :chef_server_rest
+ VALID_NAME = /^[\.\-[:alnum:]_]+$/.freeze
+ RESERVED_NAMES = /^(node|role|environment|client)$/.freeze
def self.validate_name!(name)
- unless name =~ VALID_NAME
+ unless VALID_NAME.match?(name)
raise Exceptions::InvalidDataBagName, "DataBags must have a name matching #{VALID_NAME.inspect}, you gave #{name.inspect}"
end
+ if RESERVED_NAMES.match?(name)
+ raise Exceptions::InvalidDataBagName, "DataBags may not have a name matching #{RESERVED_NAMES.inspect}, you gave #{name.inspect}"
+ end
end
# Create a new Chef::DataBag
@@ -52,22 +54,23 @@ class Chef
set_or_return(
:name,
arg,
- :regex => VALID_NAME
+ regex: VALID_NAME
)
end
- def to_hash
- result = {
- "name" => @name,
+ def to_h
+ {
+ "name" => @name,
"json_class" => self.class.name,
- "chef_type" => "data_bag",
+ "chef_type" => "data_bag",
}
- result
end
+ alias_method :to_hash, :to_h
+
# Serialize this object as a hash
def to_json(*a)
- Chef::JSONCompat.to_json(to_hash, *a)
+ Chef::JSONCompat.to_json(to_h, *a)
end
def chef_server_rest
@@ -78,12 +81,6 @@ class Chef
Chef::ServerAPI.new(Chef::Config[:chef_server_url])
end
- # Create a Chef::Role from JSON
- def self.json_create(o)
- Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::DataBag#from_hash")
- from_hash(o)
- end
-
def self.from_hash(o)
bag = new
bag.name(o["name"])
@@ -96,11 +93,12 @@ class Chef
names = []
paths.each do |path|
unless File.directory?(path)
- raise Chef::Exceptions::InvalidDataBagPath, "Data bag path '#{path}' is invalid"
+ raise Chef::Exceptions::InvalidDataBagPath, "Data bag path '#{path}' not found. Please create this directory."
end
names += Dir.glob(File.join(
- Chef::Util::PathHelper.escape_glob_dir(path), "*")).map { |f| File.basename(f) }.sort
+ Chef::Util::PathHelper.escape_glob_dir(path), "*"
+ )).map { |f| File.basename(f) }.sort
end
names.inject({}) { |h, n| h[n] = n; h }
else
@@ -123,21 +121,21 @@ class Chef
data_bag = {}
paths.each do |path|
unless File.directory?(path)
- raise Chef::Exceptions::InvalidDataBagPath, "Data bag path '#{path}' is invalid"
+ raise Chef::Exceptions::InvalidDataBagPath, "Data bag path '#{path}' not found. Please create this directory."
end
Dir.glob(File.join(Chef::Util::PathHelper.escape_glob_dir(path, name.to_s), "*.json")).inject({}) do |bag, 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
+ if data_bag.key?(item["id"]) && data_bag[item["id"]] != item
raise Chef::Exceptions::DuplicateDataBagItem, "Data bag '#{name}' has items with the same name '#{item["id"]}' but different content."
else
data_bag[item["id"]] = item
end
end
end
- return data_bag
+ data_bag
else
Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("data/#{name}")
end
@@ -155,13 +153,13 @@ class Chef
else
create
end
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => e
raise e unless e.response.code == "409"
end
self
end
- #create a data bag via RESTful API
+ # create a data bag via RESTful API
def create
chef_server_rest.post("data", self)
self
diff --git a/lib/chef/data_bag_item.rb b/lib/chef/data_bag_item.rb
index 568c511c44..94cf2a5317 100644
--- a/lib/chef/data_bag_item.rb
+++ b/lib/chef/data_bag_item.rb
@@ -2,7 +2,7 @@
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Nuo Yan (<nuo@chef.io>)
# Author:: Christopher Brown (<cb@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,15 +18,15 @@
# limitations under the License.
#
-require "forwardable"
+require "forwardable" unless defined?(Forwardable)
-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"
+require_relative "config"
+require_relative "mixin/params_validate"
+require_relative "mixin/from_file"
+require_relative "data_bag"
+require_relative "mash"
+require_relative "server_api"
+require_relative "json_compat"
class Chef
class DataBagItem
@@ -36,9 +36,7 @@ class Chef
include Chef::Mixin::FromFile
include Chef::Mixin::ParamsValidate
- VALID_ID = /^[\.\-[:alnum:]_]+$/
-
- attr_accessor :chef_server_rest
+ VALID_ID = /^[\.\-[:alnum:]_]+$/.freeze
def self.validate_id!(id_str)
if id_str.nil? || ( id_str !~ VALID_ID )
@@ -66,18 +64,16 @@ class Chef
Chef::ServerAPI.new(Chef::Config[:chef_server_url])
end
- def raw_data
- @raw_data
- end
-
def validate_id!(id_str)
self.class.validate_id!(id_str)
end
def raw_data=(new_data)
+ new_data = Mash.new(new_data)
unless new_data.respond_to?(:[]) && new_data.respond_to?(:keys)
raise Exceptions::ValidationFailed, "Data Bag Items must contain a Hash or Mash!"
end
+
validate_id!(new_data["id"])
@raw_data = new_data
end
@@ -86,7 +82,7 @@ class Chef
set_or_return(
:data_bag,
arg,
- :regex => /^[\-[:alnum:]_]+$/
+ regex: /^[\-[:alnum:]_]+$/
)
end
@@ -95,7 +91,7 @@ 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.key?("id")
raise Exceptions::ValidationFailed, "You must have declared what bag this item belongs to!" unless data_bag
id = raw_data["id"]
@@ -106,21 +102,23 @@ class Chef
"data_bag_item_#{data_bag_name}_#{id}"
end
- def to_hash
- result = self.raw_data.dup
+ def to_h
+ result = raw_data.dup
result["chef_type"] = "data_bag_item"
- result["data_bag"] = self.data_bag.to_s
+ result["data_bag"] = data_bag.to_s
result
end
+ alias_method :to_hash, :to_h
+
# Serialize this object as a hash
def to_json(*a)
result = {
- "name" => object_name,
+ "name" => object_name,
"json_class" => self.class.name,
- "chef_type" => "data_bag_item",
- "data_bag" => data_bag,
- "raw_data" => raw_data,
+ "chef_type" => "data_bag_item",
+ "data_bag" => data_bag,
+ "raw_data" => raw_data,
}
Chef::JSONCompat.to_json(result, *a)
end
@@ -132,30 +130,25 @@ class Chef
item = new
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"])
+ item.raw_data = h["raw_data"]
else
item.raw_data = h
end
item
end
- # Create a Chef::DataBagItem from JSON
- def self.json_create(o)
- Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::DataBagItem#from_hash")
- 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
def self.load(data_bag, name)
if Chef::Config[:solo_legacy_mode]
bag = Chef::DataBag.load(data_bag)
raise Exceptions::InvalidDataBagItemID, "Item #{name} not found in data bag #{data_bag}. Other items found: #{bag.keys.join(", ")}" unless bag.include?(name)
+
item = bag[name]
else
item = Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("data/#{data_bag}/#{name}")
end
- if item.kind_of?(DataBagItem)
+ if item.is_a?(DataBagItem)
item
else
item = from_hash(item)
@@ -164,7 +157,7 @@ class Chef
end
end
- def destroy(data_bag = self.data_bag(), databag_item = name)
+ def destroy(data_bag = self.data_bag, databag_item = name)
chef_server_rest.delete("data/#{data_bag}/#{databag_item}")
end
@@ -177,8 +170,9 @@ class Chef
else
r.put("data/#{data_bag}/#{item_id}", self)
end
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => e
raise e unless e.response.code == "404"
+
r.post("data/#{data_bag}", self)
end
self
@@ -191,9 +185,9 @@ class Chef
end
def ==(other)
- other.respond_to?(:to_hash) &&
+ other.respond_to?(:to_h) &&
other.respond_to?(:data_bag) &&
- (other.to_hash == to_hash) &&
+ (other.to_h == to_h) &&
(other.data_bag.to_s == data_bag.to_s)
end
@@ -203,11 +197,11 @@ class Chef
end
def inspect
- "data_bag_item[#{data_bag.inspect}, #{raw_data['id'].inspect}, #{raw_data.inspect}]"
+ "data_bag_item[#{data_bag.inspect}, #{raw_data["id"].inspect}, #{raw_data.inspect}]"
end
def pretty_print(pretty_printer)
- pretty_printer.pp({ "data_bag_item('#{data_bag}', '#{id}')" => self.to_hash })
+ pretty_printer.pp({ "data_bag_item('#{data_bag}', '#{id}')" => to_hash })
end
def id
diff --git a/lib/chef/data_collector.rb b/lib/chef/data_collector.rb
index dbb0b3771a..8d76f8a7b2 100644
--- a/lib/chef/data_collector.rb
+++ b/lib/chef/data_collector.rb
@@ -2,7 +2,7 @@
# Author:: Adam Leff (<adamleff@chef.io>)
# Author:: Ryan Cragun (<ryan@chef.io>)
#
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,380 +18,273 @@
# limitations under the License.
#
-require "uri"
-require "chef/event_dispatch/base"
-require "chef/data_collector/messages"
-require "chef/data_collector/resource_report"
-require "ostruct"
+require_relative "server_api"
+require_relative "http/simple_json"
+require_relative "event_dispatch/base"
+autoload :Set, "set"
+require_relative "data_collector/run_end_message"
+require_relative "data_collector/run_start_message"
+require_relative "data_collector/config_validation"
+require_relative "data_collector/error_handlers"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
-
- # == Chef::DataCollector
- # Provides methods for determinine whether a reporter should be registered.
class DataCollector
- def self.register_reporter?
- Chef::Config[:data_collector][:server_url] &&
- !Chef::Config[:why_run] &&
- self.reporter_enabled_for_current_mode?
- end
-
- def self.reporter_enabled_for_current_mode?
- if Chef::Config[:solo] || Chef::Config[:local_mode]
- acceptable_modes = [:solo, :both]
- else
- acceptable_modes = [:client, :both]
- end
-
- acceptable_modes.include?(Chef::Config[:data_collector][:mode])
- end
-
- # == Chef::DataCollector::Reporter
- # Provides an event handler that can be registered to report on Chef
- # run data. Unlike the existing Chef::ResourceReporter event handler,
- # the DataCollector handler is not tied to a Chef Server / Chef Reporting
- # and exports its data through a webhook-like mechanism to a configured
- # endpoint.
+ # The DataCollector is mode-agnostic reporting tool which can be used with
+ # server-based and solo-based clients. It can report to a file, to an
+ # authenticated Chef Automate reporting endpoint, or to a user-supplied
+ # webhook. It sends two messages: one at the start of the run and one
+ # at the end of the run. Most early failures in the actual Chef::Client itself
+ # are reported, but parsing of the client.rb must have succeeded and some code
+ # in Chef::Application could throw so early as to prevent reporting. If
+ # exceptions are thrown both run-start and run-end messages are still sent in
+ # pairs.
+ #
class Reporter < EventDispatch::Base
- attr_reader :all_resource_reports, :status, :exception, :error_descriptions,
- :expanded_run_list, :run_context, :run_status, :http,
- :current_resource_report, :enabled
-
- def initialize
- validate_data_collector_server_url!
-
- @all_resource_reports = []
- @current_resource_loaded = nil
- @error_descriptions = {}
- @expanded_run_list = {}
- @http = Chef::HTTP.new(data_collector_server_url)
- @enabled = true
- end
+ include Chef::DataCollector::ErrorHandlers
- # see EventDispatch::Base#run_started
- # Upon receipt, we will send our run start message to the
- # configured DataCollector endpoint. Depending on whether
- # the user has configured raise_on_failure, if we cannot
- # send the message, we will either disable the DataCollector
- # Reporter for the duration of this run, or we'll raise an
- # exception.
- def run_started(current_run_status)
- update_run_status(current_run_status)
-
- disable_reporter_on_error do
- send_to_data_collector(
- Chef::DataCollector::Messages.run_start_message(current_run_status).to_json
- )
- end
- end
+ # @return [Chef::RunList::RunListExpansion] the expanded run list
+ attr_reader :expanded_run_list
- # see EventDispatch::Base#run_completed
- # Upon receipt, we will send our run completion message to the
- # configured DataCollector endpoint.
- def run_completed(node)
- send_run_completion(status: "success")
- end
+ # @return [Chef::RunStatus] the run status
+ attr_reader :run_status
- # see EventDispatch::Base#run_failed
- def run_failed(exception)
- send_run_completion(status: "failure")
- end
+ # @return [Chef::Node] the chef node
+ attr_reader :node
- # see EventDispatch::Base#converge_start
- # Upon receipt, we stash the run_context for use at the
- # end of the run in order to determine what resource+action
- # combinations have not yet fired so we can report on
- # unprocessed resources.
- def converge_start(run_context)
- @run_context = run_context
- end
+ # @return [Set<Hash>] the accumulated list of deprecation warnings
+ attr_reader :deprecations
- # see EventDispatch::Base#converge_complete
- # At the end of the converge, we add any unprocessed resources
- # to our report list.
- def converge_complete
- detect_unprocessed_resources
- end
+ # @return [Chef::ActionCollection] the action collection object
+ attr_reader :action_collection
- # see EventDispatch::Base#converge_failed
- # At the end of the converge, we add any unprocessed resources
- # to our report list
- def converge_failed(exception)
- detect_unprocessed_resources
- end
+ # @return [Chef::EventDispatch::Dispatcher] the event dispatcher
+ attr_reader :events
- # see EventDispatch::Base#resource_current_state_loaded
- # Create a new ResourceReport instance that we'll use to track
- # the state of this resource during the run. Nested resources are
- # ignored as they are assumed to be an inline resource of a custom
- # resource, and we only care about tracking top-level resources.
- def resource_current_state_loaded(new_resource, action, current_resource)
- return if nested_resource?(new_resource)
- update_current_resource_report(create_resource_report(new_resource, action, current_resource))
+ # @param events [Chef::EventDispatch::Dispatcher] the event dispatcher
+ def initialize(events)
+ @events = events
+ @expanded_run_list = {}
+ @deprecations = Set.new
end
- # see EventDispatch::Base#resource_up_to_date
- # Mark our ResourceReport status accordingly
- def resource_up_to_date(new_resource, action)
- current_resource_report.up_to_date unless nested_resource?(new_resource)
+ # Hook to grab the run_status. We also make the decision to run or not run here (our
+ # config has been parsed so we should know if we need to run, we unregister if we do
+ # not want to run).
+ #
+ # (see EventDispatch::Base#run_start)
+ #
+ def run_start(chef_version, run_status)
+ events.unregister(self) unless Chef::DataCollector::ConfigValidation.should_be_enabled?
+ @run_status = run_status
end
- # see EventDispatch::Base#resource_skipped
- # If this is a top-level resource, we create a ResourceReport
- # instance (because a skipped resource does not trigger the
- # resource_current_state_loaded event), and flag it as skipped.
- def resource_skipped(new_resource, action, conditional)
- return if nested_resource?(new_resource)
-
- resource_report = create_resource_report(new_resource, action)
- resource_report.skipped(conditional)
- update_current_resource_report(resource_report)
+ # Hook to grab the node object after it has been successfully loaded
+ #
+ # (see EventDispatch::Base#node_load_success)
+ #
+ def node_load_success(node)
+ @node = node
end
- # see EventDispatch::Base#resource_updated
- # Flag the current ResourceReport instance as updated (as long as it's
- # a top-level resource).
- def resource_updated(new_resource, action)
- current_resource_report.updated unless nested_resource?(new_resource)
+ # The expanded run list is stored for later use by the run_completed
+ # event and message.
+ #
+ # (see EventDispatch::Base#run_list_expanded)
+ #
+ def run_list_expanded(run_list_expansion)
+ @expanded_run_list = run_list_expansion
end
- # see EventDispatch::Base#resource_failed
- # Flag the current ResourceReport as failed and supply the exception as
- # long as it's a top-level resource, and update the run error text
- # with the proper Formatter.
- def resource_failed(new_resource, action, exception)
- current_resource_report.failed(exception) unless nested_resource?(new_resource)
- update_error_description(
- Formatters::ErrorMapper.resource_failed(
- new_resource,
- action,
- exception
- ).for_json
- )
+ # Hook event to register with the action_collection if we are still enabled.
+ #
+ # This is also how we wire up to the action_collection since it passes itself as the argument.
+ #
+ # (see EventDispatch::Base#action_collection_registration)
+ #
+ def action_collection_registration(action_collection)
+ @action_collection = action_collection
+ action_collection.register(self)
end
- # see EventDispatch::Base#resource_completed
- # Mark the ResourceReport instance as finished (for timing details).
- # This marks the end of this resource during this run.
- def resource_completed(new_resource)
- if current_resource_report && !nested_resource?(new_resource)
- current_resource_report.finish
- add_resource_report(current_resource_report)
- update_current_resource_report(nil)
- end
- end
+ # - Creates and writes our NodeUUID back to the node object
+ # - Sanity checks the data collector
+ # - Sends the run start message
+ # - If the run_start message fails, this may disable the rest of data collection or fail hard
+ #
+ # (see EventDispatch::Base#run_started)
+ #
+ def run_started(run_status)
+ Chef::DataCollector::ConfigValidation.validate_server_url!
+ Chef::DataCollector::ConfigValidation.validate_output_locations!
- # see EventDispatch::Base#run_list_expanded
- # The expanded run list is stored for later use by the run_completed
- # event and message.
- def run_list_expanded(run_list_expansion)
- @expanded_run_list = run_list_expansion
+ send_run_start
end
- # see EventDispatch::Base#run_list_expand_failed
- # The run error text is updated with the output of the appropriate
- # formatter.
- def run_list_expand_failed(node, exception)
- update_error_description(
- Formatters::ErrorMapper.run_list_expand_failed(
- node,
- exception
- ).for_json
- )
+ # Hook event to accumulating deprecation messages
+ #
+ # (see EventDispatch::Base#deprecation)
+ #
+ def deprecation(message, location = caller(2..2)[0])
+ @deprecations << { message: message.message, url: message.url, location: message.location }
end
- # see EventDispatch::Base#cookbook_resolution_failed
- # The run error text is updated with the output of the appropriate
- # formatter.
- def cookbook_resolution_failed(expanded_run_list, exception)
- update_error_description(
- Formatters::ErrorMapper.cookbook_resolution_failed(
- expanded_run_list,
- exception
- ).for_json
- )
+ # Hook to send the run completion message with a status of success
+ #
+ # (see EventDispatch::Base#run_completed)
+ #
+ def run_completed(node)
+ send_run_completion("success")
end
- # see EventDispatch::Base#cookbook_sync_failed
- # The run error text is updated with the output of the appropriate
- # formatter.
- def cookbook_sync_failed(cookbooks, exception)
- update_error_description(
- Formatters::ErrorMapper.cookbook_sync_failed(
- cookbooks,
- exception
- ).for_json
- )
+ # Hook to send the run completion message with a status of failed
+ #
+ # (see EventDispatch::Base#run_failed)
+ #
+ def run_failed(exception)
+ send_run_completion("failure")
end
private
+ # Construct a http client for either the main data collector or for the http output_locations.
#
- # Yields to the passed-in block (which is expected to be some interaction
- # with the DataCollector endpoint). If some communication failure occurs,
- # either disable any future communications to the DataCollector endpoint, or
- # raise an exception (if the user has set
- # Chef::Config.data_collector.raise_on_failure to true.)
+ # Note that based on the token setting either the main data collector and all the http output_locations
+ # are going to all require chef-server authentication or not. There is no facility to mix-and-match on
+ # a per-url basis.
#
- # @param block [Proc] A ruby block to run. Ignored if a command is given.
+ # @param url [String] the string url to connect to
+ # @returns [Chef::HTTP] the appropriate Chef::HTTP subclass instance to use
#
- def disable_reporter_on_error
- yield
- rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET,
- Errno::ECONNREFUSED, EOFError, Net::HTTPBadResponse,
- Net::HTTPHeaderSyntaxError, Net::ProtocolError, OpenSSL::SSL::SSLError,
- Errno::EHOSTDOWN => e
- disable_data_collector_reporter
- code = if e.respond_to?(:response) && e.response.code
- e.response.code.to_s
- else
- "Exception Code Empty"
- end
-
- msg = "Error while reporting run start to Data Collector. " \
- "URL: #{data_collector_server_url} " \
- "Exception: #{code} -- #{e.message} "
-
- if Chef::Config[:data_collector][:raise_on_failure]
- Chef::Log.error(msg)
- raise
+ def setup_http_client(url)
+ if Chef::Config[:data_collector][:token].nil?
+ Chef::ServerAPI.new(url, validate_utf8: false)
else
- Chef::Log.warn(msg)
+ Chef::HTTP::SimpleJSON.new(url, validate_utf8: false)
end
end
- def send_to_data_collector(message)
- return unless data_collector_accessible?
-
- Chef::Log.debug("data_collector_reporter: POSTing the following message to #{data_collector_server_url}: #{message}")
- http.post(nil, message, headers)
- end
-
- #
- # Send any messages to the DataCollector endpoint that are necessary to
- # indicate the run has completed. Currently, two messages are sent:
+ # Handle POST'ing data to the data collector. Note that this is a totally separate concern
+ # from the array of URI's in the extra configured output_locations.
#
- # - An "action" message with the node object indicating it's been updated
- # - An "run_converge" (i.e. RunEnd) message with details about the run,
- # what resources were modified/up-to-date/skipped, etc.
+ # On failure this will unregister the data collector (if there are no other configured output_locations)
+ # and optionally will either silently continue or fail hard depending on configuration.
#
- # @param opts [Hash] Additional details about the run, such as its success/failure.
+ # @param message [Hash] message to send
#
- def send_run_completion(opts)
- # If run_status is nil we probably failed before the client triggered
- # the run_started callback. In this case we'll skip updating because
- # we have nothing to report.
- return unless run_status
-
- send_to_data_collector(
- Chef::DataCollector::Messages.run_end_message(
- run_status: run_status,
- expanded_run_list: expanded_run_list,
- resources: all_resource_reports,
- status: opts[:status],
- error_descriptions: error_descriptions
- ).to_json
- )
- end
+ def send_to_data_collector(message)
+ return unless Chef::Config[:data_collector][:server_url]
- def headers
- headers = { "Content-Type" => "application/json" }
+ @http ||= setup_http_client(Chef::Config[:data_collector][:server_url])
+ @http.post(nil, message, headers)
+ rescue => e
+ # Do not disable data collector reporter if additional output_locations have been specified
+ events.unregister(self) unless Chef::Config[:data_collector][:output_locations]
- unless data_collector_token.nil?
- headers["x-data-collector-token"] = data_collector_token
- headers["x-data-collector-auth"] = "version=1.0"
+ begin
+ code = e&.response&.code.to_s
+ rescue
+ # i really don't care
end
- headers
- end
-
- def data_collector_server_url
- Chef::Config[:data_collector][:server_url]
- end
+ code ||= "No HTTP Code"
- def data_collector_token
- Chef::Config[:data_collector][:token]
- end
-
- def add_resource_report(resource_report)
- @all_resource_reports << OpenStruct.new(
- resource: resource_report.new_resource,
- action: resource_report.action,
- report_data: resource_report.to_hash
- )
- end
+ msg = "Error while reporting run start to Data Collector. URL: #{Chef::Config[:data_collector][:server_url]} Exception: #{code} -- #{e.message} "
- def disable_data_collector_reporter
- @enabled = false
+ if Chef::Config[:data_collector][:raise_on_failure]
+ Chef::Log.error(msg)
+ raise
+ else
+ if code == "404"
+ # Make the message non-scary for folks who don't have automate:
+ msg << " (This is normal if you do not have #{ChefUtils::Dist::Automate::PRODUCT})"
+ Chef::Log.debug(msg)
+ else
+ Chef::Log.warn(msg)
+ end
+ end
end
- def data_collector_accessible?
- @enabled
+ # Process sending the configured message to all the extra output locations.
+ #
+ # @param message [Hash] message to send
+ #
+ def send_to_output_locations(message)
+ return unless Chef::Config[:data_collector][:output_locations]
+
+ Chef::DataCollector::ConfigValidation.validate_output_locations!
+ Chef::Config[:data_collector][:output_locations].each do |type, locations|
+ Array(locations).each do |location|
+ send_to_file_location(location, message) if type == :files
+ send_to_http_location(location, message) if type == :urls
+ end
+ end
end
- def update_run_status(run_status)
- @run_status = run_status
+ # Sends a single message to a file, rendered as JSON.
+ #
+ # @param file_name [String] the file to write to
+ # @param message [Hash] the message to render as JSON
+ #
+ def send_to_file_location(file_name, message)
+ File.open(File.expand_path(file_name), "a") do |fh|
+ fh.puts Chef::JSONCompat.to_json(message, validate_utf8: false)
+ end
end
- def update_current_resource_report(resource_report)
- @current_resource_report = resource_report
+ # Sends a single message to a http uri, rendered as JSON. Maintains a cache of Chef::HTTP
+ # objects to use on subsequent requests.
+ #
+ # @param http_url [String] the configured http uri string endpoint to send to
+ # @param message [Hash] the message to render as JSON
+ #
+ def send_to_http_location(http_url, message)
+ @http_output_locations_clients[http_url] ||= setup_http_client(http_url)
+ @http_output_locations_clients[http_url].post(nil, message, headers)
+ rescue
+ # FIXME: we do all kinds of complexity to deal with errors in send_to_data_collector and we just don't care here, which feels like
+ # like poor behavior on several different levels, at least its a warn now... (I don't quite understand why it was written this way)
+ Chef::Log.warn("Data collector failed to send to URL location #{http_url}. Please check your configured data_collector.output_locations")
end
- def update_error_description(discription_hash)
- @error_descriptions = discription_hash
+ # @return [Boolean] if we've sent a run_start message yet
+ def sent_run_start?
+ !!@sent_run_start
end
- def create_resource_report(new_resource, action, current_resource = nil)
- Chef::DataCollector::ResourceReport.new(
- new_resource,
- action,
- current_resource
- )
+ # Send the run start message to the configured server or output locations
+ #
+ def send_run_start
+ message = Chef::DataCollector::RunStartMessage.construct_message(self)
+ send_to_data_collector(message)
+ send_to_output_locations(message)
+ @sent_run_start = true
end
- def detect_unprocessed_resources
- # create a Set containing all resource+action combinations from
- # the Resource Collection
- collection_resources = Set.new
- run_context.resource_collection.all_resources.each do |resource|
- Array(resource.action).each do |action|
- collection_resources.add([resource, action])
- end
- end
-
- # Delete from the Set any resource+action combination we have
- # already processed.
- all_resource_reports.each do |report|
- collection_resources.delete([report.resource, report.action])
- end
-
- # The items remaining in the Set are unprocessed resource+actions,
- # so we'll create new resource reports for them which default to
- # a state of "unprocessed".
- collection_resources.each do |resource, action|
- add_resource_report(create_resource_report(resource, action))
- end
- end
+ # Send the run completion message to the configured server or output locations
+ #
+ # @param status [String] Either "success" or "failed"
+ #
+ def send_run_completion(status)
+ # this is necessary to send a run_start message when we fail before the run_started chef event.
+ # we adhere to a contract that run_start + run_completion events happen in pairs.
+ send_run_start unless sent_run_start?
- # If we are getting messages about a resource while we are in the middle of
- # another resource's update, we assume that the nested resource is just the
- # implementation of a provider, and we want to hide it from the reporting
- # output.
- def nested_resource?(new_resource)
- @current_resource_report && @current_resource_report.new_resource != new_resource
+ message = Chef::DataCollector::RunEndMessage.construct_message(self, status)
+ send_to_data_collector(message)
+ send_to_output_locations(message)
end
- def validate_data_collector_server_url!
- raise Chef::Exceptions::ConfigurationError,
- "Chef::Config[:data_collector][:server_url] is empty. Please supply a valid URL." if data_collector_server_url.empty?
+ # @return [Hash] HTTP headers for the data collector endpoint
+ def headers
+ headers = { "Content-Type" => "application/json" }
- begin
- uri = URI(data_collector_server_url)
- rescue URI::InvalidURIError
- raise Chef::Exceptions::ConfigurationError, "Chef::Config[:data_collector][:server_url] (#{data_collector_server_url}) is not a valid URI."
+ unless Chef::Config[:data_collector][:token].nil?
+ headers["x-data-collector-token"] = Chef::Config[:data_collector][:token]
+ headers["x-data-collector-auth"] = "version=1.0"
end
- raise Chef::Exceptions::ConfigurationError,
- "Chef::Config[:data_collector][:server_url] (#{data_collector_server_url}) is a URI with no host. Please supply a valid URL." if uri.host.nil?
+ headers
end
end
end
diff --git a/lib/chef/data_collector/config_validation.rb b/lib/chef/data_collector/config_validation.rb
new file mode 100644
index 0000000000..1cdc400f48
--- /dev/null
+++ b/lib/chef/data_collector/config_validation.rb
@@ -0,0 +1,140 @@
+#
+# Copyright:: Copyright (c) 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" unless defined?(URI)
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class DataCollector
+
+ # @api private
+ module ConfigValidation
+ class << self
+ def validate_server_url!
+ # if we have a server_url set we ALWAYS validate it, and we MUST have an output_location set to skip server_url validation
+ # (having output_locations set and no server_url is valid, but both of them unset blows up in here)
+ return if !Chef::Config[:data_collector][:server_url] && Chef::Config[:data_collector][:output_locations]
+
+ begin
+ uri = URI(Chef::Config[:data_collector][:server_url])
+ rescue
+ raise Chef::Exceptions::ConfigurationError, "Chef::Config[:data_collector][:server_url] (#{Chef::Config[:data_collector][:server_url]}) is not a valid URI."
+ end
+
+ if uri.host.nil?
+ raise Chef::Exceptions::ConfigurationError,
+ "Chef::Config[:data_collector][:server_url] (#{Chef::Config[:data_collector][:server_url]}) is a URI with no host. Please supply a valid URL."
+ end
+ end
+
+ def validate_output_locations!
+ # not having an output_location set at all is fine, we just skip it then
+ output_locations = Chef::Config[:data_collector][:output_locations]
+ return unless output_locations
+
+ # but deliberately setting an empty output_location we consider to be an error (XXX: but should we?)
+ unless valid_hash_with_keys?(output_locations, :urls, :files)
+ raise Chef::Exceptions::ConfigurationError,
+ "Chef::Config[:data_collector][:output_locations] is empty. Please supply an hash of valid URLs and / or local file paths."
+ end
+
+ # loop through all the types and locations and validate each one-by-one
+ output_locations.each do |type, locations|
+ Array(locations).each do |location|
+ validate_url!(location) if type == :urls
+ validate_file!(location) if type == :files
+ end
+ end
+ end
+
+ # Main logic controlling the data collector being enabled or disabled:
+ #
+ # * disabled in why-run mode
+ # * disabled when `Chef::Config[:data_collector][:mode]` excludes the solo-vs-client mode
+ # * disabled if there is no server_url or no output_locations to log to
+ # * enabled if there is a configured output_location even without a token
+ # * disabled in solo mode if the user did not configure the auth token
+ #
+ # @return [Boolean] true if the data collector should be enabled
+ #
+ def should_be_enabled?
+ running_mode = ( Chef::Config[:solo_legacy_mode] || Chef::Config[:local_mode] ) ? :solo : :client
+ want_mode = Chef::Config[:data_collector][:mode]
+
+ case
+ when Chef::Config[:why_run]
+ Chef::Log.trace("data collector is disabled for why run mode")
+ false
+ when (want_mode != :both) && running_mode != want_mode
+ Chef::Log.trace("data collector is configured to only run in #{Chef::Config[:data_collector][:mode]} modes, disabling it")
+ false
+ when !(Chef::Config[:data_collector][:server_url] || Chef::Config[:data_collector][:output_locations])
+ Chef::Log.trace("Neither data collector URL or output locations have been configured, disabling data collector")
+ false
+ when running_mode == :client && Chef::Config[:data_collector][:token]
+ Chef::Log.warn("Data collector token authentication is not recommended for client-server mode. " \
+ "Please upgrade #{ChefUtils::Dist::Server::PRODUCT} to 12.11 or later and remove the token from your config file " \
+ "to use key based authentication instead")
+ true
+ when Chef::Config[:data_collector][:output_locations] && !valid_hash_with_keys?(Chef::Config[:data_collector][:output_locations], :urls)
+ # we can run fine to a file without a token, even in solo mode.
+ unless valid_hash_with_keys?(Chef::Config[:data_collector][:output_locations], :files)
+ raise Chef::Exceptions::ConfigurationError,
+ "Chef::Config[:data_collector][:output_locations] is empty. Please supply an hash of valid URLs and / or local file paths."
+ end
+
+ true
+ when running_mode == :solo && !Chef::Config[:data_collector][:token]
+ # we are in solo mode and are not logging to a file, so must have a token
+ Chef::Log.trace("Data collector token must be configured to use #{ChefUtils::Dist::Automate::PRODUCT} data collector with #{ChefUtils::Dist::Solo::PRODUCT}")
+ false
+ else
+ true
+ end
+ end
+
+ private
+
+ # validate an output_location file
+ def validate_file!(file)
+ return true if Chef::Config.path_accessible?(File.expand_path(file))
+
+ raise Chef::Exceptions::ConfigurationError,
+ "Chef::Config[:data_collector][:output_locations][:files] contains the location #{file}, which is a non existent file path."
+ end
+
+ # validate an output_location url
+ def validate_url!(url)
+ URI(url)
+ rescue
+ raise Chef::Exceptions::ConfigurationError,
+ "Chef::Config[:data_collector][:output_locations][:urls] contains the url #{url} which is not valid."
+ end
+
+ # Validate the hash contains at least one of the given keys.
+ #
+ # @param hash [Hash] the hash to be validated.
+ # @param keys [Array] an array of keys to check existence of in the hash.
+ # @return [Boolean] true if the hash contains any of the given keys.
+ #
+ def valid_hash_with_keys?(hash, *keys)
+ hash.is_a?(Hash) && keys.any? { |k| hash.key?(k) }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/data_collector/error_handlers.rb b/lib/chef/data_collector/error_handlers.rb
new file mode 100644
index 0000000000..5cda3bde10
--- /dev/null
+++ b/lib/chef/data_collector/error_handlers.rb
@@ -0,0 +1,116 @@
+#
+# Copyright:: Copyright (c) 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 DataCollector
+
+ # This module isolates the handling of collecting error descriptions to insert into the data_collector
+ # report output. For very early errors it is responsible for collecting the node_name for the report
+ # to use. For all failure conditions that have an ErrorMapper it collects the output.
+ #
+ # No external code should call anything in this module directly.
+ #
+ # @api private
+ #
+ module ErrorHandlers
+
+ # @return [String] the fallback node name if we do NOT have a node due to early failures
+ attr_reader :node_name
+
+ # @return [Hash] JSON-formatted error description from the Chef::Formatters::ErrorMapper
+ def error_description
+ @error_description ||= {}
+ end
+
+ # This is an exceptionally "early" failure that results in not having a valid Chef::Node object,
+ # so it must capture the node_name from the config.rb
+ #
+ # (see EventDispatch::Base#registration_failed)
+ #
+ def registration_failed(node_name, exception, config)
+ description = Formatters::ErrorMapper.registration_failed(node_name, exception, config)
+ @node_name = node_name
+ @error_description = description.for_json
+ end
+
+ # This is an exceptionally "early" failure that results in not having a valid Chef::Node object,
+ # so it must capture the node_name from the config.rb
+ #
+ # (see EventDispatch::Base#node_load_failed)
+ #
+ def node_load_failed(node_name, exception, config)
+ description = Formatters::ErrorMapper.node_load_failed(node_name, exception, config)
+ @node_name = node_name
+ @error_description = description.for_json
+ end
+
+ # This is an "early" failure during run_list expansion
+ #
+ # (see EventDispatch::Base#run_list_expand_failed)
+ #
+ def run_list_expand_failed(node, exception)
+ description = Formatters::ErrorMapper.run_list_expand_failed(node, exception)
+ @error_description = description.for_json
+ end
+
+ # This is an "early" failure during cookbook resolution / depsolving / talking to cookbook_version endpoint on a server
+ #
+ # (see EventDispatch::Base#cookbook_resolution_failed)
+ #
+ def cookbook_resolution_failed(expanded_run_list, exception)
+ description = Formatters::ErrorMapper.cookbook_resolution_failed(expanded_run_list, exception)
+ @error_description = description.for_json
+ end
+
+ # This is an "early" failure during cookbook synchronization
+ #
+ # (see EventDispatch::Base#cookbook_sync_failed)
+ #
+ def cookbook_sync_failed(cookbooks, exception)
+ description = Formatters::ErrorMapper.cookbook_sync_failed(cookbooks, exception)
+ @error_description = description.for_json
+ end
+
+ # This failure happens during library loading / attribute file parsing, etc.
+ #
+ # (see EventDispatch::Base#file_load_failed)
+ #
+ def file_load_failed(path, exception)
+ description = Formatters::ErrorMapper.file_load_failed(path, exception)
+ @error_description = description.for_json
+ end
+
+ # This failure happens at converge time during recipe parsing
+ #
+ # (see EventDispatch::Base#recipe_not_failed)
+ #
+ def recipe_not_found(exception)
+ description = Formatters::ErrorMapper.file_load_failed(nil, exception)
+ @error_description = description.for_json
+ end
+
+ # This is a normal resource failure event during compile/converge phases
+ #
+ # (see EventDispatch::Base#resource_failed)
+ #
+ def resource_failed(new_resource, action, exception)
+ description = Formatters::ErrorMapper.resource_failed(new_resource, action, exception)
+ @error_description = description.for_json
+ end
+ end
+ end
+end
diff --git a/lib/chef/data_collector/message_helpers.rb b/lib/chef/data_collector/message_helpers.rb
new file mode 100644
index 0000000000..5441404bd6
--- /dev/null
+++ b/lib/chef/data_collector/message_helpers.rb
@@ -0,0 +1,50 @@
+#
+# Copyright:: Copyright (c) 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 DataCollector
+
+ # This is for shared code between the run_start_message and run_end_message modules.
+ #
+ # No external code should call this module directly
+ #
+ # @api private
+ #
+ module MessageHelpers
+ private
+
+ # The organization name the node is associated with. For Chef Solo runs the default
+ # is "chef_solo" which can be overridden by the user.
+ #
+ # @return [String] Chef organization associated with the node
+ #
+ def organization
+ if solo_run?
+ # configurable fake organization name for chef-solo users
+ Chef::Config[:data_collector][:organization]
+ else
+ Chef::Config[:chef_server_url].match(%r{/+organizations/+([^\s/]+)}).nil? ? "unknown_organization" : $1
+ end
+ end
+
+ # @return [Boolean] True if we're in a chef-solo/chef-zero or legacy chef-solo run
+ def solo_run?
+ Chef::Config[:solo_legacy_mode] || Chef::Config[:local_mode]
+ end
+ end
+ end
+end
diff --git a/lib/chef/data_collector/messages.rb b/lib/chef/data_collector/messages.rb
deleted file mode 100644
index 8c2a84b580..0000000000
--- a/lib/chef/data_collector/messages.rb
+++ /dev/null
@@ -1,96 +0,0 @@
-#
-# Author:: Adam Leff (<adamleff@chef.io)
-# Author:: Ryan Cragun (<ryan@chef.io>)
-#
-# 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 "json"
-require "securerandom"
-require_relative "messages/helpers"
-
-class Chef
- class DataCollector
- module Messages
- extend Helpers
-
- #
- # Message payload that is sent to the DataCollector server at the
- # start of a Chef run.
- #
- # @param run_status [Chef::RunStatus] The RunStatus instance for this node/run.
- #
- # @return [Hash] A hash containing the run start message data.
- #
- def self.run_start_message(run_status)
- {
- "chef_server_fqdn" => chef_server_fqdn(run_status),
- "entity_uuid" => node_uuid,
- "id" => run_status.run_id,
- "message_version" => "1.0.0",
- "message_type" => "run_start",
- "node_name" => run_status.node.name,
- "organization_name" => organization,
- "run_id" => run_status.run_id,
- "source" => collector_source,
- "start_time" => run_status.start_time.utc.iso8601,
- }
- end
-
- #
- # Message payload that is sent to the DataCollector server at the
- # end of a Chef run.
- #
- # @param reporter_data [Hash] Data supplied by the Reporter, such as run_status, resource counts, etc.
- #
- # @return [Hash] A hash containing the run end message data.
- #
- def self.run_end_message(reporter_data)
- run_status = reporter_data[:run_status]
-
- message = {
- "chef_server_fqdn" => chef_server_fqdn(run_status),
- "entity_uuid" => node_uuid,
- "expanded_run_list" => reporter_data[:expanded_run_list],
- "id" => run_status.run_id,
- "message_version" => "1.0.0",
- "message_type" => "run_converge",
- "node" => run_status.node,
- "node_name" => run_status.node.name,
- "organization_name" => organization,
- "resources" => reporter_data[:resources].map(&:report_data),
- "run_id" => run_status.run_id,
- "run_list" => run_status.node.run_list.for_json,
- "start_time" => run_status.start_time.utc.iso8601,
- "end_time" => run_status.end_time.utc.iso8601,
- "source" => collector_source,
- "status" => reporter_data[:status],
- "total_resource_count" => reporter_data[:resources].count,
- "updated_resource_count" => reporter_data[:resources].select { |r| r.report_data["status"] == "updated" }.count,
- }
-
- message["error"] = {
- "class" => run_status.exception.class,
- "message" => run_status.exception.message,
- "backtrace" => run_status.exception.backtrace,
- "description" => reporter_data[:error_descriptions],
- } if run_status.exception
-
- message
- end
- end
- end
-end
diff --git a/lib/chef/data_collector/messages/helpers.rb b/lib/chef/data_collector/messages/helpers.rb
deleted file mode 100644
index c0c700f847..0000000000
--- a/lib/chef/data_collector/messages/helpers.rb
+++ /dev/null
@@ -1,161 +0,0 @@
-#
-# Author:: Adam Leff (<adamleff@chef.io)
-# Author:: Ryan Cragun (<ryan@chef.io>)
-#
-# 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.
-#
-
-class Chef
- class DataCollector
- module Messages
- module Helpers
- #
- # Fully-qualified domain name of the Chef Server configured in Chef::Config
- # If the chef_server_url cannot be parsed as a URI, the node["fqdn"] attribute
- # will be returned, or "localhost" if the run_status is unavailable to us.
- #
- # @param run_status [Chef::RunStatus] The RunStatus object for this Chef Run.
- #
- # @return [String] FQDN of the configured Chef Server, or node/localhost if not found.
- #
- def chef_server_fqdn(run_status)
- if !Chef::Config[:chef_server_url].nil?
- URI(Chef::Config[:chef_server_url]).host
- elsif !Chef::Config[:node_name].nil?
- Chef::Config[:node_name]
- else
- "localhost"
- end
- end
-
- #
- # The organization name the node is associated with. For Chef Solo runs, a
- # user-configured organization string is returned, or the string "chef_solo"
- # if such a string is not configured.
- #
- # @return [String] Organization to which the node is associated
- #
- def organization
- solo_run? ? data_collector_organization : chef_server_organization
- end
-
- #
- # Returns the user-configured organization, or "chef_solo" if none is configured.
- #
- # This is only used when Chef is run in Solo mode.
- #
- # @return [String] Data-collector-specific organization used when running in Chef Solo
- #
- def data_collector_organization
- Chef::Config[:data_collector][:organization] || "chef_solo"
- end
-
- #
- # Return the organization assumed by the configured chef_server_url.
- #
- # We must parse this from the Chef::Config[:chef_server_url] because a node
- # has no knowledge of an organization or to which organization is belongs.
- #
- # If we cannot determine the organization, we return "unknown_organization"
- #
- # @return [String] shortname of the Chef Server organization
- #
- def chef_server_organization
- return "unknown_organization" unless Chef::Config[:chef_server_url]
-
- Chef::Config[:chef_server_url].match(%r{/+organizations/+(\w+)}).nil? ? "unknown_organization" : $1
- end
-
- #
- # The source of the data collecting during this run, used by the
- # DataCollector endpoint to determine if Chef was in Solo mode or not.
- #
- # @return [String] "chef_solo" if in Solo mode, "chef_client" if in Client mode
- #
- def collector_source
- solo_run? ? "chef_solo" : "chef_client"
- end
-
- #
- # If we're running in Solo (legacy) mode, or in Solo (formerly
- # "Chef Client Local Mode"), we're considered to be in a "solo run".
- #
- # @return [Boolean] Whether we're in a solo run or not
- #
- def solo_run?
- Chef::Config[:solo] || Chef::Config[:local_mode]
- end
-
- #
- # Returns a UUID that uniquely identifies this node for reporting reasons.
- #
- # The node is read in from disk if it exists, or it's generated if it does
- # does not exist.
- #
- # @return [String] UUID for the node
- #
- def node_uuid
- read_node_uuid || generate_node_uuid
- end
-
- #
- # Generates a UUID for the node via SecureRandom.uuid and writes out
- # metadata file so the UUID persists between runs.
- #
- # @return [String] UUID for the node
- #
- def generate_node_uuid
- uuid = SecureRandom.uuid
- update_metadata("node_uuid", uuid)
-
- uuid
- end
-
- #
- # Reads in the node UUID from the node metadata file
- #
- # @return [String] UUID for the node
- #
- def read_node_uuid
- metadata["node_uuid"]
- end
-
- #
- # Returns the DataCollector metadata for this node
- #
- # If the metadata file does not exist in the file cache path,
- # an empty hash will be returned.
- #
- # @return [Hash] DataCollector metadata for this node
- #
- def metadata
- JSON.load(Chef::FileCache.load(metadata_filename))
- rescue Chef::Exceptions::FileNotFound
- {}
- end
-
- def update_metadata(key, value)
- updated_metadata = metadata.tap { |x| x[key] = value }
- Chef::FileCache.store(metadata_filename, updated_metadata.to_json, 0644)
- end
-
- def metadata_filename
- "data_collector_metadata.json"
- end
- end
- end
- end
-end
diff --git a/lib/chef/data_collector/resource_report.rb b/lib/chef/data_collector/resource_report.rb
deleted file mode 100644
index dcaf9c8e44..0000000000
--- a/lib/chef/data_collector/resource_report.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-#
-# Author:: Adam Leff (<adamleff@chef.io>)
-# Author:: Ryan Cragun (<ryan@chef.io>)
-#
-# 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.
-#
-
-class Chef
- class DataCollector
- class ResourceReport
-
- attr_reader :action, :elapsed_time, :new_resource, :status
- attr_accessor :conditional, :current_resource, :exception
-
- def initialize(new_resource, action, current_resource = nil)
- @new_resource = new_resource
- @action = action
- @current_resource = current_resource
- @status = "unprocessed"
- end
-
- def skipped(conditional)
- @status = "skipped"
- @conditional = conditional
- end
-
- def updated
- @status = "updated"
- end
-
- def failed(exception)
- @current_resource = nil
- @status = "failed"
- @exception = exception
- end
-
- def up_to_date
- @status = "up-to-date"
- end
-
- def finish
- @elapsed_time = new_resource.elapsed_time
- end
-
- def elapsed_time_in_milliseconds
- elapsed_time.nil? ? nil : (elapsed_time * 1000).to_i
- end
-
- def potentially_changed?
- %w{updated failed}.include?(status)
- end
-
- def to_hash
- hash = {
- "type" => new_resource.resource_name.to_sym,
- "name" => new_resource.name.to_s,
- "id" => new_resource.identity.to_s,
- "after" => new_resource.state_for_resource_reporter,
- "before" => current_resource ? current_resource.state_for_resource_reporter : {},
- "duration" => elapsed_time_in_milliseconds.to_s,
- "delta" => new_resource.respond_to?(:diff) && potentially_changed? ? new_resource.diff : "",
- "ignore_failure" => new_resource.ignore_failure,
- "result" => action.to_s,
- "status" => status,
- }
-
- if new_resource.cookbook_name
- hash["cookbook_name"] = new_resource.cookbook_name
- hash["cookbook_version"] = new_resource.cookbook_version.version
- hash["recipe_name"] = new_resource.recipe_name
- end
-
- hash["conditional"] = conditional.to_text if status == "skipped"
- hash["error_message"] = exception.message unless exception.nil?
-
- hash
- end
- alias :to_h :to_hash
- alias :for_json :to_hash
- end
- end
-end
diff --git a/lib/chef/data_collector/run_end_message.rb b/lib/chef/data_collector/run_end_message.rb
new file mode 100644
index 0000000000..1900effa26
--- /dev/null
+++ b/lib/chef/data_collector/run_end_message.rb
@@ -0,0 +1,191 @@
+#
+# Copyright:: Copyright (c) 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_relative "message_helpers"
+
+class Chef
+ class DataCollector
+ module RunEndMessage
+ extend Chef::DataCollector::MessageHelpers
+
+ # This module encapsulates rendering the run_end_message given the state gathered in the data_collector
+ # and the action_collection. It is deliberately a stateless module and is deliberately not mixed into
+ # the data_collector and only uses the public api methods of the data_collector and action_collection.
+ #
+ # No external code should call this module directly.
+ #
+ # @api private
+ class << self
+
+ # Construct the message payload that is sent to the DataCollector server at the
+ # end of a Chef run.
+ #
+ # @param data_collector [Chef::DataCollector::Reporter] the calling data_collector instance
+ # @param status [String] the overall status of the run, either "success" or "failure"
+ #
+ # @return [Hash] A hash containing the run end message data.
+ #
+ def construct_message(data_collector, status)
+ action_collection = data_collector.action_collection
+ run_status = data_collector.run_status
+ node = data_collector.node
+
+ message = {
+ "chef_server_fqdn" => URI(Chef::Config[:chef_server_url]).host,
+ "entity_uuid" => Chef::Config[:chef_guid],
+ "expanded_run_list" => data_collector.expanded_run_list,
+ "id" => run_status&.run_id,
+ "message_version" => "1.1.0",
+ "message_type" => "run_converge",
+ "node" => node || {},
+ "node_name" => node&.name || data_collector.node_name,
+ "organization_name" => organization,
+ "resources" => all_action_records(action_collection),
+ "run_id" => run_status&.run_id,
+ "run_list" => node&.run_list&.for_json || [],
+ "cookbooks" => ( node && node["cookbooks"] ) || {},
+ "policy_name" => node&.policy_name,
+ "policy_group" => node&.policy_group,
+ "start_time" => run_status&.start_time&.utc&.iso8601,
+ "end_time" => run_status&.end_time&.utc&.iso8601,
+ "source" => solo_run? ? "chef_solo" : "chef_client",
+ "status" => status,
+ "total_resource_count" => all_action_records(action_collection).count,
+ "updated_resource_count" => updated_resource_count(action_collection),
+ "deprecations" => data_collector.deprecations.to_a,
+ }
+
+ if run_status&.exception
+ message["error"] = {
+ "class" => run_status.exception.class,
+ "message" => run_status.exception.message,
+ "backtrace" => run_status.exception.backtrace,
+ "description" => data_collector.error_description,
+ }
+ end
+
+ message
+ end
+
+ private
+
+ # @return [Integer] the number of resources successfully updated in the chef-client run
+ def updated_resource_count(action_collection)
+ return 0 if action_collection.nil?
+
+ action_collection.filtered_collection(up_to_date: false, skipped: false, unprocessed: false, failed: false).count
+ end
+
+ # @return [Array<Chef::ActionCollection::ActionRecord>] list of all action_records for all resources
+ def action_records(action_collection)
+ return [] if action_collection.nil?
+
+ action_collection.action_records
+ end
+
+ # @return [Array<Hash>] list of all action_records rendered as a Hash for sending to JSON
+ def all_action_records(action_collection)
+ action_records(action_collection).map { |rec| action_record_for_json(rec) }
+ end
+
+ # @return [Hash] the Hash representation of the action_record for sending as JSON
+ def action_record_for_json(action_record)
+ new_resource = action_record.new_resource
+ current_resource = action_record.current_resource
+ after_resource = action_record.after_resource
+
+ hash = {
+ "type" => new_resource.resource_name.to_sym,
+ "name" => new_resource.name.to_s,
+ "id" => safe_resource_identity(new_resource),
+ "after" => safe_state_for_resource_reporter(after_resource || new_resource),
+ "before" => safe_state_for_resource_reporter(current_resource),
+ "duration" => action_record.elapsed_time.nil? ? "" : (action_record.elapsed_time * 1000).to_i.to_s,
+ "delta" => new_resource.respond_to?(:diff) && updated_or_failed?(action_record) ? new_resource.diff : "",
+ "ignore_failure" => new_resource.ignore_failure,
+ "result" => action_record.action.to_s,
+ "status" => action_record_status_for_json(action_record),
+ }
+
+ # don't use the new_resource for the after_resource if it is skipped or failed
+ if action_record.status == :skipped || action_record.status == :failed || action_record.status == :unprocessed
+ hash["after"] = {}
+ end
+
+ if new_resource.cookbook_name
+ hash["cookbook_name"] = new_resource.cookbook_name
+ hash["cookbook_version"] = new_resource.cookbook_version.version
+ hash["recipe_name"] = new_resource.recipe_name
+ end
+
+ hash["conditional"] = action_record.conditional.to_text if action_record.status == :skipped
+
+ unless action_record.exception.nil?
+ hash["error_message"] = action_record.exception.message
+
+ hash["error"] = {
+ "class" => action_record.exception.class,
+ "message" => action_record.exception.message,
+ "backtrace" => action_record.exception.backtrace,
+ "description" => action_record.error_description,
+ }
+ end
+
+ hash
+ end
+
+ # If the identity property of a resource has been lazied (via a lazy name resource) evaluating it
+ # for an unprocessed resource (where the preconditions have not been met) may cause the lazy
+ # evaluator to throw -- and would otherwise crash the data collector.
+ #
+ # @return [String] the resource's identity property
+ #
+ def safe_resource_identity(new_resource)
+ new_resource.identity.to_s
+ rescue => e
+ "unknown identity (due to #{e.class})"
+ end
+
+ # FIXME: This is likely necessary due to the same lazy issue with properties and failing resources?
+ #
+ # @return [Hash] the resource's reported state properties
+ #
+ def safe_state_for_resource_reporter(resource)
+ resource ? resource.state_for_resource_reporter : {}
+ rescue
+ {}
+ end
+
+ # Helper to convert action record status (symbols) to strings for the Data Collector server.
+ # Does a bit of necessary underscores-to-dashes conversion to comply with the Data Collector API.
+ #
+ # @return [String] resource status (
+ #
+ def action_record_status_for_json(action_record)
+ action = action_record.status.to_s
+ action = "up-to-date" if action == "up_to_date"
+ action
+ end
+
+ # @return [Boolean] True if the resource was updated or failed
+ def updated_or_failed?(action_record)
+ action_record.status == :updated || action_record.status == :failed
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/data_collector/run_start_message.rb b/lib/chef/data_collector/run_start_message.rb
new file mode 100644
index 0000000000..20ac867ef1
--- /dev/null
+++ b/lib/chef/data_collector/run_start_message.rb
@@ -0,0 +1,60 @@
+#
+# Copyright:: Copyright (c) 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_relative "message_helpers"
+
+class Chef
+ class DataCollector
+ module RunStartMessage
+ extend Chef::DataCollector::MessageHelpers
+
+ # This module encapsulates rendering the run_start_message given the state gathered in the data_collector.
+ # It is deliberately a stateless module and is deliberately not mixed into the data_collector and only
+ # uses the public api methods of the data_collector.
+ #
+ # No external code should call this module directly.
+ #
+ # @api private
+ class << self
+
+ # Construct the message payload that is sent to the DataCollector server at the
+ # start of a Chef run.
+ #
+ # @param data_collector [Chef::DataCollector::Reporter] the calling data_collector instance
+ #
+ # @return [Hash] A hash containing the run start message data.
+ #
+ def construct_message(data_collector)
+ run_status = data_collector.run_status
+ node = data_collector.node
+ {
+ "chef_server_fqdn" => URI(Chef::Config[:chef_server_url]).host,
+ "entity_uuid" => Chef::Config[:chef_guid],
+ "id" => run_status&.run_id,
+ "message_version" => "1.0.0",
+ "message_type" => "run_start",
+ "node_name" => node&.name || data_collector.node_name,
+ "organization_name" => organization,
+ "run_id" => run_status&.run_id,
+ "source" => solo_run? ? "chef_solo" : "chef_client",
+ "start_time" => run_status&.start_time&.utc&.iso8601,
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/decorator.rb b/lib/chef/decorator.rb
index 546c49baed..9f8b304d31 100644
--- a/lib/chef/decorator.rb
+++ b/lib/chef/decorator.rb
@@ -1,5 +1,5 @@
#--
-# Copyright:: Copyright 2016 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,14 +16,13 @@
#
require "delegate"
+require_relative "constants"
class Chef
class Decorator < SimpleDelegator
- NULL = ::Object.new
-
- def initialize(obj = NULL)
+ def initialize(obj = NOT_PASSED)
@__defined_methods__ = []
- super unless obj.equal?(NULL)
+ super unless obj.equal?(NOT_PASSED)
end
# if we wrap a nil then decorator.nil? should be true
@@ -38,7 +37,7 @@ class Chef
# if we wrap a Hash then decorator.kind_of?(Hash) should be true
def kind_of?(klass)
- __getobj__.kind_of?(klass) || super
+ __getobj__.is_a?(klass) || super
end
# reset our methods on the instance if the object changes under us (this also
@@ -52,7 +51,7 @@ class Chef
# adding the define_singleton_method call and @__defined_methods__ tracking
def method_missing(m, *args, &block)
r = true
- target = self.__getobj__ { r = false }
+ target = __getobj__ { r = false }
if r && target.respond_to?(m)
# these next 4 lines are the patched code
diff --git a/lib/chef/decorator/lazy.rb b/lib/chef/decorator/lazy.rb
index 71a2151d65..5badc05ade 100644
--- a/lib/chef/decorator/lazy.rb
+++ b/lib/chef/decorator/lazy.rb
@@ -1,5 +1,5 @@
#--
-# Copyright:: Copyright 2016 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,7 +15,7 @@
# limitations under the License.
#
-require "chef/decorator"
+require_relative "../decorator"
class Chef
class Decorator
diff --git a/lib/chef/decorator/lazy_array.rb b/lib/chef/decorator/lazy_array.rb
index dc8ea832ee..9859c49d77 100644
--- a/lib/chef/decorator/lazy_array.rb
+++ b/lib/chef/decorator/lazy_array.rb
@@ -1,5 +1,5 @@
#--
-# Copyright:: Copyright 2016 Chef Software, Inc.
+# Copyright:: Copyright (c) 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/decorator/lazy"
+require_relative "lazy"
class Chef
class Decorator
# Lazy Array around Lazy Objects
#
- # This only lazys access through `#[]`. In order to implement #each we need to
- # know how many items we have and what their indexes are, so we'd have to evalute
+ # This makes access lazy through `#[]`. In order to implement #each we need to
+ # know how many items we have and what their indexes are, so we'd have to evaluate
# the proc which makes that impossible. You can call methods like #each and the
# decorator will forward the method, but item access will not be lazy.
#
diff --git a/lib/chef/decorator/unchain.rb b/lib/chef/decorator/unchain.rb
index 8093c70f4c..30179d4e63 100644
--- a/lib/chef/decorator/unchain.rb
+++ b/lib/chef/decorator/unchain.rb
@@ -38,22 +38,6 @@ class Chef
__path__.push(key)
@delegate_sd_obj.public_send(__method__, *__path__, value)
end
-
- # unfortunately we have to support method_missing for node.set_unless.foo.bar = 'baz' notation
- def method_missing(symbol, *args)
- if symbol == :to_ary
- merged_attributes.send(symbol, *args)
- elsif args.empty?
- Chef.log_deprecation %q{method access to node attributes (node.foo.bar) is deprecated and will be removed in Chef 13, please use bracket syntax (node["foo"]["bar"])}
- self[symbol]
- elsif symbol.to_s =~ /=$/
- Chef.log_deprecation %q{method setting of node attributes (node.foo="bar") is deprecated and will be removed in Chef 13, please use bracket syntax (node["foo"]="bar")}
- key_to_set = symbol.to_s[/^(.+)=$/, 1]
- self[key_to_set] = (args.length == 1 ? args[0] : args)
- else
- raise NoMethodError, "Undefined node attribute or method `#{symbol}' on `node'"
- end
- end
end
end
end
diff --git a/lib/chef/delayed_evaluator.rb b/lib/chef/delayed_evaluator.rb
index 25cd3a17b8..df734c8303 100644
--- a/lib/chef/delayed_evaluator.rb
+++ b/lib/chef/delayed_evaluator.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser <jkeiser@chef.io>
-# Copyright:: Copyright 2015-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/lib/chef/deprecated.rb b/lib/chef/deprecated.rb
new file mode 100644
index 0000000000..992876c17d
--- /dev/null
+++ b/lib/chef/deprecated.rb
@@ -0,0 +1,262 @@
+#--
+# Copyright:: Copyright (c) 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_relative "mixin/convert_to_class_name"
+
+# Structured deprecations have a unique URL associated with them, which must exist before the deprecation is merged.
+class Chef
+ class Deprecated
+
+ class << self
+ include Chef::Mixin::ConvertToClassName
+
+ def create(type, message, location)
+ Chef::Deprecated.const_get(convert_to_class_name(type.to_s)).new(message, location)
+ end
+ end
+
+ class Base
+ BASE_URL = "https://docs.chef.io/deprecations_".freeze
+
+ attr_reader :message, :location
+
+ def initialize(msg = nil, location = nil)
+ @message = msg
+ @location = location
+ end
+
+ def link
+ "Please see #{url} for further details and information on how to correct this problem."
+ end
+
+ # Render the URL for the deprecation documentation page.
+ #
+ # @return [String]
+ def url
+ "#{BASE_URL}#{self.class.doc_page}/"
+ end
+
+ # Render the user-visible message for this deprecation.
+ #
+ # @return [String]
+ def to_s
+ "Deprecation CHEF-#{self.class.deprecation_id} from #{location}\n\n #{message}\n\n#{link}"
+ end
+
+ # Check if this deprecation has been silenced.
+ #
+ # @return [Boolean]
+ def silenced?
+ # Check if all warnings have been silenced.
+ return true if Chef::Config[:silence_deprecation_warnings] == true
+ # Check if this warning has been silenced by the config.
+ return true if Chef::Config[:silence_deprecation_warnings].any? do |silence_spec|
+ if silence_spec.is_a? Integer
+ # Integers can end up matching the line number in the `location` string
+ silence_spec = "CHEF-#{silence_spec}"
+ else
+ # Just in case someone uses a symbol in the config by mistake.
+ silence_spec = silence_spec.to_s
+ end
+ # Check for a silence by deprecation name, or by location.
+ self.class.deprecation_key == silence_spec || self.class.deprecation_id.to_s == silence_spec || "chef-#{self.class.deprecation_id}" == silence_spec.downcase || location.include?(silence_spec)
+ end
+ # check if this warning has been silenced by inline comment.
+ return true if location =~ /^(.*?):(\d+):in/ && begin
+ # Don't buffer the whole file in memory, so read it one line at a time.
+ line_no = $2.to_i
+ location_file = ::File.open($1)
+ (line_no - 1).times { location_file.readline } # Read all the lines we don't care about.
+ relevant_line = location_file.readline
+ relevant_line.match?(/#.*chef:silence_deprecation($|[^:]|:#{self.class.deprecation_key})/)
+ end
+
+ false
+ end
+
+ class << self
+ attr_reader :deprecation_id, :doc_page
+
+ # Return the deprecation key as would be used with {Chef::Deprecated.create}.
+ #
+ # @return [String]
+ def deprecation_key
+ Chef::Mixin::ConvertToClassName.convert_to_snake_case(name, "Chef::Deprecated")
+ end
+
+ # Set the ID and documentation page path for this deprecation.
+ #
+ # Used in subclasses to set the data for each type of deprecation.
+ #
+ # @example
+ # class MyDeprecation < Base
+ # target 123, "my_deprecation"
+ # end
+ # @param id [Integer] Deprecation ID number. This must be unique among
+ # all deprecations.
+ # @param page [String, nil] Optional documentation page path. If not
+ # specified, the deprecation key is used.
+ # @return [void]
+ def target(id, page = nil)
+ @deprecation_id = id
+ @doc_page = page || deprecation_key.to_s
+ end
+ end
+ end
+
+ class InternalApi < Base
+ target 0
+ end
+
+ class JsonAutoInflate < Base
+ target 1
+ end
+
+ class ExitCode < Base
+ target 2
+ end
+
+ # id 3 has been deleted
+
+ class Attributes < Base
+ target 4
+ end
+
+ class CustomResource < Base
+ target 5, "custom_resource_cleanups"
+ end
+
+ class EasyInstall < Base
+ target 6
+ end
+
+ class VerifyFile < Base
+ target 7
+ end
+
+ class SupportsProperty < Base
+ target 8
+ end
+
+ class ChefRest < Base
+ target 9
+ end
+
+ class DnfPackageAllowDowngrade < Base
+ target 10
+ end
+
+ class PropertyNameCollision < Base
+ target 11
+ end
+
+ class LaunchdHashProperty < Base
+ target 12
+ end
+
+ class ChefPlatformMethods < Base
+ target 13
+ end
+
+ class RunCommand < Base
+ target 14
+ end
+
+ class PackageMisc < Base
+ target 15
+ end
+
+ class MultiresourceMatch < Base
+ target 16
+ end
+
+ class UseInlineResources < Base
+ target 17
+ end
+
+ class LocalListen < Base
+ target 18
+ end
+
+ class NamespaceCollisions < Base
+ target 19
+ end
+
+ class DeployResource < Base
+ target 21
+ end
+
+ class ErlResource < Base
+ target 22
+ end
+
+ class FreebsdPkgProvider < Base
+ target 23
+ end
+
+ # id 25 was deleted
+
+ # id 3694 was deleted
+
+ # Returned when using the deprecated option on a property
+ class Property < Base
+ target 24
+
+ def to_s
+ "Deprecated resource property used from #{location}\n\n #{message}\n\nPlease consult the resource documentation for more information."
+ end
+ end
+
+ class ShellOut < Base
+ target 26
+ end
+
+ class LocaleLcAll < Base
+ target 27
+ end
+
+ class ChefSugar < Base
+ target 28
+ end
+
+ class KnifeBootstrapApis < Base
+ target 29
+ end
+
+ class ArchiveFileIntegerFileMode < Base
+ target 30
+ end
+
+ class ResourceNameWithoutProvides < Base
+ target 31
+ end
+
+ class AttributeBlacklistConfiguration < Base
+ target 32
+ end
+
+ class Generic < Base
+ def url
+ "https://docs.chef.io/chef_deprecations_client/"
+ end
+
+ def to_s
+ "Deprecation from #{location}\n\n #{message}\n\n#{link}"
+ end
+ end
+ end
+end
diff --git a/lib/chef/deprecation/mixin/template.rb b/lib/chef/deprecation/mixin/template.rb
deleted file mode 100644
index 0c902123cf..0000000000
--- a/lib/chef/deprecation/mixin/template.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-#
-# Author:: Serdar Sutay (<serdar@chef.io>)
-# Copyright:: Copyright 2013-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 "tempfile"
-require "erubis"
-
-class Chef
- module Deprecation
- module Mixin
- # == Deprecation::Provider::Mixin::Template
- # This module contains the deprecated functions of
- # Chef::Mixin::Template. These functions are refactored to different
- # components. They are frozen and will be removed in Chef 13.
- #
-
- module Template
- def render_template(template, context)
- begin
- eruby = Erubis::Eruby.new(template)
- output = eruby.evaluate(context)
- rescue Object => e
- raise TemplateError.new(e, template, context)
- end
- Tempfile.open("chef-rendered-template") do |tempfile|
- tempfile.print(output)
- tempfile.close
- yield tempfile
- end
- end
- end
- end
- end
-end
diff --git a/lib/chef/deprecation/provider/cookbook_file.rb b/lib/chef/deprecation/provider/cookbook_file.rb
deleted file mode 100644
index d6e8a7566e..0000000000
--- a/lib/chef/deprecation/provider/cookbook_file.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-#
-# Author:: Serdar Sutay (<serdar@chef.io>)
-# Copyright:: Copyright 2013-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.
-#
-
-class Chef
- module Deprecation
- module Provider
-
- # == Deprecation::Provider::CookbookFile
- # This module contains the deprecated functions of
- # Chef::Provider::CookbookFile. These functions are refactored to
- # different components. They are frozen and will be removed in Chef 13.
- #
- module CookbookFile
-
- def file_cache_location
- @file_cache_location ||= begin
- cookbook = run_context.cookbook_collection[resource_cookbook]
- cookbook.preferred_filename_on_disk_location(node, :files, @new_resource.source, @new_resource.path)
- end
- end
-
- def resource_cookbook
- @new_resource.cookbook || @new_resource.cookbook_name
- end
-
- def content_stale?
- ( ! ::File.exist?(@new_resource.path)) || ( ! compare_content)
- end
-
- def backup_new_resource
- if ::File.exists?(@new_resource.path)
- backup @new_resource.path
- end
- end
-
- end
- end
- end
-end
diff --git a/lib/chef/deprecation/provider/file.rb b/lib/chef/deprecation/provider/file.rb
deleted file mode 100644
index edb0052fdf..0000000000
--- a/lib/chef/deprecation/provider/file.rb
+++ /dev/null
@@ -1,198 +0,0 @@
-#
-# Author:: Serdar Sutay (<serdar@chef.io>)
-# Copyright:: Copyright 2013-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/util/path_helper"
-
-class Chef
- module Deprecation
- module Provider
-
- # == Deprecation::Provider::File
- # This module contains the deprecated functions of
- # Chef::Provider::File. These functions are refactored to different
- # components. They are frozen and will be removed in Chef 13.
- #
- module File
-
- def diff_current_from_content(new_content)
- result = nil
- Tempfile.open("chef-diff") do |file|
- file.write new_content
- file.close
- result = diff_current file.path
- end
- result
- end
-
- def is_binary?(path)
- ::File.open(path) do |file|
-
- buff = file.read(Chef::Config[:diff_filesize_threshold])
- buff = "" if buff.nil?
- return buff !~ /^[\r[:print:]]*$/
- end
- end
-
- def diff_current(temp_path)
- suppress_resource_reporting = false
-
- return [ "(diff output suppressed by config)" ] if Chef::Config[:diff_disabled]
- return [ "(no temp file with new content, diff output suppressed)" ] unless ::File.exists?(temp_path) # should never happen?
-
- # solaris does not support diff -N, so create tempfile to diff against if we are creating a new file
- target_path = if ::File.exists?(@current_resource.path)
- @current_resource.path
- else
- suppress_resource_reporting = true # suppress big diffs going to resource reporting service
- tempfile = Tempfile.new("chef-tempfile")
- tempfile.path
- end
-
- diff_filesize_threshold = Chef::Config[:diff_filesize_threshold]
- diff_output_threshold = Chef::Config[:diff_output_threshold]
-
- if ::File.size(target_path) > diff_filesize_threshold || ::File.size(temp_path) > diff_filesize_threshold
- return [ "(file sizes exceed #{diff_filesize_threshold} bytes, diff output suppressed)" ]
- end
-
- # MacOSX(BSD?) diff will *sometimes* happily spit out nasty binary diffs
- return [ "(current file is binary, diff output suppressed)"] if is_binary?(target_path)
- return [ "(new content is binary, diff output suppressed)"] if is_binary?(temp_path)
-
- begin
- # -u: Unified diff format
- result = shell_out("diff -u #{target_path} #{temp_path}" )
- rescue Exception => e
- # Should *not* receive this, but in some circumstances it seems that
- # an exception can be thrown even using shell_out instead of shell_out!
- return [ "Could not determine diff. Error: #{e.message}" ]
- end
-
- # diff will set a non-zero return code even when there's
- # valid stdout results, if it encounters something unexpected
- # So as long as we have output, we'll show it.
- if not result.stdout.empty?
- if result.stdout.length > diff_output_threshold
- [ "(long diff of over #{diff_output_threshold} characters, diff output suppressed)" ]
- else
- val = result.stdout.split("\n")
- val.delete("\\ No newline at end of file")
- @new_resource.diff(val.join("\\n")) unless suppress_resource_reporting
- val
- end
- elsif not result.stderr.empty?
- [ "Could not determine diff. Error: #{result.stderr}" ]
- else
- [ "(no diff)" ]
- end
- end
-
- def setup_acl
- return if Chef::Platform.windows?
- acl_scanner = ScanAccessControl.new(@new_resource, @current_resource)
- acl_scanner.set_all!
- end
-
- def compare_content
- checksum(@current_resource.path) == new_resource_content_checksum
- end
-
- def set_content
- unless compare_content
- description = []
- description << "update content in file #{@new_resource.path} from #{short_cksum(@current_resource.checksum)} to #{short_cksum(new_resource_content_checksum)}"
- description << diff_current_from_content(@new_resource.content)
- converge_by(description) do
- backup @new_resource.path if ::File.exists?(@new_resource.path)
- ::File.open(@new_resource.path, "w") { |f| f.write @new_resource.content }
- Chef::Log.info("#{@new_resource} contents updated")
- end
- end
- end
-
- def update_new_file_state(path = @new_resource.path)
- if !::File.directory?(path)
- @new_resource.checksum(checksum(path))
- end
-
- if Chef::Platform.windows?
- # TODO: To work around CHEF-3554, add support for Windows
- # equivalent, or implicit resource reporting won't work for
- # Windows.
- return
- end
-
- acl_scanner = ScanAccessControl.new(@new_resource, @new_resource)
- acl_scanner.set_all!
- end
-
- def set_all_access_controls
- if access_controls.requires_changes?
- converge_by(access_controls.describe_changes) do
- access_controls.set_all
- #Update file state with new access values
- update_new_file_state
- end
- end
- end
-
- def deploy_tempfile
- Tempfile.open(::File.basename(@new_resource.name)) do |tempfile|
- yield tempfile
-
- temp_res = Chef::Resource::CookbookFile.new(@new_resource.name)
- temp_res.path(tempfile.path)
- ac = Chef::FileAccessControl.new(temp_res, @new_resource, self)
- ac.set_all!
- FileUtils.mv(tempfile.path, @new_resource.path)
- end
- end
-
- def backup(file = nil)
- file ||= @new_resource.path
- if @new_resource.backup != false && @new_resource.backup > 0 && ::File.exist?(file)
- time = Time.now
- savetime = time.strftime("%Y%m%d%H%M%S")
- backup_filename = "#{@new_resource.path}.chef-#{savetime}"
- backup_filename = backup_filename.sub(/^([A-Za-z]:)/, "") #strip drive letter on Windows
- # if :file_backup_path is nil, we fallback to the old behavior of
- # keeping the backup in the same directory. We also need to to_s it
- # so we don't get a type error around implicit to_str conversions.
- prefix = Chef::Config[:file_backup_path].to_s
- backup_path = ::File.join(prefix, backup_filename)
- FileUtils.mkdir_p(::File.dirname(backup_path)) if Chef::Config[:file_backup_path]
- FileUtils.cp(file, backup_path, :preserve => true)
- Chef::Log.info("#{@new_resource} backed up to #{backup_path}")
-
- # Clean up after the number of backups
- slice_number = @new_resource.backup
- backup_files = Dir[Chef::Util::PathHelper.escape_glob_dir(prefix, ".#{@new_resource.path}") + ".chef-*"].sort { |a, b| b <=> a }
- if backup_files.length >= @new_resource.backup
- remainder = backup_files.slice(slice_number..-1)
- remainder.each do |backup_to_delete|
- FileUtils.rm(backup_to_delete)
- Chef::Log.info("#{@new_resource} removed backup at #{backup_to_delete}")
- end
- end
- end
- end
-
- end
- end
- end
-end
diff --git a/lib/chef/deprecation/provider/remote_directory.rb b/lib/chef/deprecation/provider/remote_directory.rb
deleted file mode 100644
index 5e5188f28b..0000000000
--- a/lib/chef/deprecation/provider/remote_directory.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-#
-# Author:: Serdar Sutay (<serdar@chef.io>)
-# Copyright:: Copyright 2013-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.
-#
-
-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_dir(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
deleted file mode 100644
index aefb04752e..0000000000
--- a/lib/chef/deprecation/provider/remote_file.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-#
-# Author:: Serdar Sutay (<serdar@chef.io>)
-# Copyright:: Copyright 2013-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.
-#
-
-class Chef
- module Deprecation
- module Provider
-
- # == Deprecation::Provider::RemoteFile
- # This module contains the deprecated functions of
- # Chef::Provider::RemoteFile. These functions are refactored to different
- # components. They are frozen and will be removed in Chef 13.
- #
- module RemoteFile
-
- def current_resource_matches_target_checksum?
- @new_resource.checksum && @current_resource.checksum && @current_resource.checksum =~ /^#{Regexp.escape(@new_resource.checksum)}/
- end
-
- def matches_current_checksum?(candidate_file)
- Chef::Log.debug "#{@new_resource} checking for file existence of #{@new_resource.path}"
- if ::File.exists?(@new_resource.path)
- Chef::Log.debug "#{@new_resource} file exists at #{@new_resource.path}"
- @new_resource.checksum(checksum(candidate_file.path))
- Chef::Log.debug "#{@new_resource} target checksum: #{@current_resource.checksum}"
- Chef::Log.debug "#{@new_resource} source checksum: #{@new_resource.checksum}"
-
- @new_resource.checksum == @current_resource.checksum
- else
- Chef::Log.debug "#{@new_resource} creating #{@new_resource.path}"
- false
- end
- end
-
- def backup_new_resource
- if ::File.exists?(@new_resource.path)
- Chef::Log.debug "#{@new_resource} checksum changed from #{@current_resource.checksum} to #{@new_resource.checksum}"
- backup @new_resource.path
- end
- end
-
- def source_file(source, current_checksum, &block)
- if absolute_uri?(source)
- fetch_from_uri(source, &block)
- elsif !Chef::Config[:solo_legacy_mode]
- fetch_from_chef_server(source, current_checksum, &block)
- else
- fetch_from_local_cookbook(source, &block)
- end
- end
-
- def http_client_opts(source)
- opts = {}
- # CHEF-3140
- # 1. If it's already compressed, trying to compress it more will
- # probably be counter-productive.
- # 2. Some servers are misconfigured so that you GET $URL/file.tgz but
- # they respond with content type of tar and content encoding of gzip,
- # which tricks Chef::REST into decompressing the response body. In this
- # case you'd end up with a tar archive (no gzip) named, e.g., foo.tgz,
- # which is not what you wanted.
- if @new_resource.path =~ /gz$/ || source =~ /gz$/
- opts[:disable_gzip] = true
- end
- opts
- end
-
- end
- end
- end
-end
diff --git a/lib/chef/deprecation/provider/template.rb b/lib/chef/deprecation/provider/template.rb
deleted file mode 100644
index ea5a880798..0000000000
--- a/lib/chef/deprecation/provider/template.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-#
-# Author:: Serdar Sutay (<serdar@chef.io>)
-# Copyright:: Copyright 2013-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/deprecation/mixin/template"
-
-class Chef
- module Deprecation
- module Provider
-
- # == Deprecation::Provider::Template
- # This module contains the deprecated functions of
- # Chef::Provider::Template. These functions are refactored to different
- # components. They are frozen and will be removed in Chef 13.
- #
- module Template
-
- include Chef::Deprecation::Mixin::Template
-
- def template_finder
- @template_finder ||= begin
- Chef::Provider::TemplateFinder.new(run_context, cookbook_name, node)
- end
- end
-
- def template_location
- @template_file_cache_location ||= begin
- template_finder.find(@new_resource.source, :local => @new_resource.local, :cookbook => @new_resource.cookbook)
- end
- end
-
- def resource_cookbook
- @new_resource.cookbook || @new_resource.cookbook_name
- end
-
- def rendered(rendered_template)
- @new_resource.checksum(checksum(rendered_template.path))
- Chef::Log.debug("Current content's checksum: #{@current_resource.checksum}")
- Chef::Log.debug("Rendered content's checksum: #{@new_resource.checksum}")
- end
-
- def content_matches?
- @current_resource.checksum == @new_resource.checksum
- end
-
- end
- end
- end
-end
diff --git a/lib/chef/deprecation/warnings.rb b/lib/chef/deprecation/warnings.rb
index 411e95ea39..f83101ca3e 100644
--- a/lib/chef/deprecation/warnings.rb
+++ b/lib/chef/deprecation/warnings.rb
@@ -1,6 +1,6 @@
#
# Author:: Serdar Sutay (<serdar@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,13 +20,15 @@ class Chef
module Deprecation
module Warnings
+ require_relative "../version"
+ require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
def add_deprecation_warnings_for(method_names)
method_names.each do |name|
define_method(name) do |*args|
- 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)
+ message = "Method '#{name}' of '#{self.class}' is deprecated. It will be removed in #{ChefUtils::Dist::Infra::PRODUCT} #{Chef::VERSION.to_i.next}."
+ message << " Please update your cookbooks accordingly."
+ Chef.deprecated(:internal_api, message)
super(*args)
end
end
diff --git a/lib/chef/digester.rb b/lib/chef/digester.rb
index 6e4588a661..36df63f3a1 100644
--- a/lib/chef/digester.rb
+++ b/lib/chef/digester.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Daniel DeLeo (<dan@kallistec.com>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# Copyright:: Copyright 2009-2016, Daniel DeLeo
# License:: Apache License, Version 2.0
#
@@ -18,8 +18,9 @@
# limitations under the License.
#
-require "openssl"
-require "singleton"
+autoload :OpenSSL, "openssl"
+autoload :Digest, "digest"
+require "singleton" unless defined?(Singleton)
class Chef
class Digester
@@ -39,9 +40,9 @@ class Chef
def generate_checksum(file)
if file.is_a?(StringIO)
- checksum_io(file, OpenSSL::Digest::SHA256.new)
+ checksum_io(file, OpenSSL::Digest.new("SHA256"))
else
- checksum_file(file, OpenSSL::Digest::SHA256.new)
+ checksum_file(file, OpenSSL::Digest.new("SHA256"))
end
end
@@ -50,11 +51,11 @@ class Chef
end
def generate_md5_checksum_for_file(file)
- checksum_file(file, OpenSSL::Digest::MD5.new)
+ checksum_file(file, ::Digest::MD5.new)
end
def generate_md5_checksum(io)
- checksum_io(io, OpenSSL::Digest::MD5.new)
+ checksum_io(io, ::Digest::MD5.new)
end
private
diff --git a/lib/chef/dsl.rb b/lib/chef/dsl.rb
index 1fa0099e91..6893298d1d 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_relative "dsl/recipe"
+require_relative "dsl/platform_introspection"
+require_relative "dsl/data_query"
+require_relative "dsl/include_recipe"
+require_relative "dsl/include_attribute"
+require_relative "dsl/registry_helper"
diff --git a/lib/chef/dsl/audit.rb b/lib/chef/dsl/audit.rb
deleted file mode 100644
index 98271dc5cb..0000000000
--- a/lib/chef/dsl/audit.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-#
-# Author:: Tyler Ball (<tball@chef.io>)
-# Copyright:: Copyright 2014-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/exceptions"
-
-class Chef
- module DSL
- module Audit
-
- # Can encompass tests in a `control` block or `describe` block
- # Adds the controls group and block (containing controls to execute) to the runner's list of pending examples
- def control_group(*args, &block)
- raise Chef::Exceptions::NoAuditsProvided unless block
-
- name = args[0]
- if name.nil? || name.empty?
- raise Chef::Exceptions::AuditNameMissing
- elsif run_context.audits.has_key?(name)
- raise Chef::Exceptions::AuditControlGroupDuplicate.new(name)
- end
-
- # This DSL will only work in the Recipe class because that exposes the cookbook_name
- cookbook_name = self.cookbook_name
- metadata = {
- 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],
- }
-
- run_context.audits[name] = Struct.new(:args, :block, :metadata).new(args, block, metadata)
- end
-
- end
- end
-end
diff --git a/lib/chef/dsl/chef_provisioning.rb b/lib/chef/dsl/chef_provisioning.rb
deleted file mode 100644
index a91d297d02..0000000000
--- a/lib/chef/dsl/chef_provisioning.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2015-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.
-#
-
-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/chef_vault.rb b/lib/chef/dsl/chef_vault.rb
new file mode 100644
index 0000000000..031627c358
--- /dev/null
+++ b/lib/chef/dsl/chef_vault.rb
@@ -0,0 +1,84 @@
+#
+# Author: Joshua Timberman <joshua@chef.io>
+#
+# Copyright:: Copyright (c) Chef Software Inc.
+# Copyright:: 2014-2015 Bloomberg Finance L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+autoload :ChefVault, "chef-vault"
+require_relative "data_query"
+
+class Chef
+ module DSL
+ module ChefVault
+ include Chef::DSL::DataQuery
+
+ # Helper method which provides a Recipe/Resource DSL for wrapping
+ # creation of {ChefVault::Item}.
+ # @note
+ # Falls back to normal data bag item loading if the item is not
+ # actually a Chef Vault item. This is controlled via
+ # +node['chef-vault']['databag_fallback']+.
+ # @example
+ # item = chef_vault_item('secrets', 'bacon')
+ # log 'Yeah buddy!' if item['_default']['type']
+ # @param [String] bag Name of the data bag to load from.
+ # @param [String] id Identifier of the data bag item to load.
+ def chef_vault_item(bag, id)
+ if ::ChefVault::Item.vault?(bag, id)
+ ::ChefVault::Item.load(bag, id)
+ elsif node["chef-vault"]["databag_fallback"]
+ data_bag_item(bag, id)
+ else
+ raise "Trying to load a regular data bag item #{id} from #{bag}, and databag_fallback is disabled"
+ end
+ end
+
+ # Helper method that allows for listing the ids of a vault in a recipe.
+ # This method is needed because data_bag() returns the keys along with
+ # the items, so this method strips out the keys for users so that they
+ # don't have to do it in their recipes.
+ # @example
+ # ids = chef_vault('secrets')
+ # log 'Yeah buddy!' if ids[0] == 'bacon'
+ # @param [String] bag Name of the data bag to load from.
+ # @return [Array]
+ def chef_vault(bag)
+ raise "'#{bag}' is not a vault" unless Chef::DataBag.list.include? bag
+
+ pattern = Regexp.new(/_keys$/).freeze
+ data_bag(bag).each_with_object([]) do |id, acc|
+ acc << id unless pattern.match?(id)
+ end
+ end
+
+ # Helper method which provides an environment wrapper for a data bag.
+ # This allows for easy access to current environment secrets inside
+ # of an item.
+ # @example
+ # item = chef_vault_item_for_environment('secrets', 'bacon')
+ # log 'Yeah buddy!' if item['type'] == 'applewood_smoked'
+ # @param [String] bag Name of the data bag to load from.
+ # @param [String] id Identifier of the data bag item to load.
+ # @return [Hash]
+ def chef_vault_item_for_environment(bag, id)
+ item = chef_vault_item(bag, id)
+ return {} unless item[node.chef_environment]
+
+ item[node.chef_environment]
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/chef/dsl/cheffish.rb b/lib/chef/dsl/cheffish.rb
index 03290b3674..91b87663c7 100644
--- a/lib/chef/dsl/cheffish.rb
+++ b/lib/chef/dsl/cheffish.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/lib/chef/dsl/core.rb b/lib/chef/dsl/core.rb
deleted file mode 100644
index 11507857cf..0000000000
--- a/lib/chef/dsl/core.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-#--
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Christopher Walters (<cw@chef.io>)
-# Copyright:: Copyright 2008-2016, 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/dsl/declare_resource"
-require "chef/dsl/universal"
-require "chef/mixin/notifying_block"
-require "chef/mixin/lazy_module_include"
-
-class Chef
- module DSL
- # Part of a family of DSL mixins.
- #
- # Chef::DSL::Recipe mixes into Recipes and LWRP Providers.
- # - this does not target core chef resources and providers.
- # - this is restricted to recipe/resource/provider context where a resource collection exists.
- # - cookbook authors should typically include modules into here.
- #
- # Chef::DSL::Core mixes into Recipes, LWRP Providers and Core Providers
- # - this adds cores providers on top of the Recipe DSL.
- # - this is restricted to recipe/resource/provider context where a resource collection exists.
- # - core chef authors should typically include modules into here.
- #
- # Chef::DSL::Universal mixes into Recipes, LWRP Resources+Providers, Core Resources+Providers, and Attributes files.
- # - this adds resources and attributes files.
- # - do not add helpers which manipulate the resource collection.
- # - this is for general-purpose stuff that is useful nearly everywhere.
- # - it also pollutes the namespace of nearly every context, watch out.
- #
- module Core
- include Chef::DSL::Universal
- include Chef::DSL::DeclareResource
- include Chef::Mixin::NotifyingBlock
- extend Chef::Mixin::LazyModuleInclude
- end
- end
-end
diff --git a/lib/chef/dsl/data_query.rb b/lib/chef/dsl/data_query.rb
index b966885724..3b15affb5b 100644
--- a/lib/chef/dsl/data_query.rb
+++ b/lib/chef/dsl/data_query.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,18 +16,18 @@
# 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_relative "../search/query"
+Chef.autoload :DataBag, File.expand_path("../data_bag", __dir__)
+Chef.autoload :DataBagItem, File.expand_path("../data_bag_item", __dir__)
+require_relative "../encrypted_data_bag_item"
+require_relative "../encrypted_data_bag_item/check_encrypted"
class Chef
module DSL
- # ==Chef::DSL::DataQuery
- # Provides DSL for querying data from the chef-server via search or data
- # bag.
+ # Provides DSL helper methods for querying the search interface, data bag
+ # interface or node interface.
+ #
module DataQuery
include Chef::EncryptedDataBagItem::CheckEncrypted
@@ -38,7 +38,7 @@ class Chef
if Kernel.block_given? || args.length >= 4
Chef::Search::Query.new.search(*args, &block)
else
- results = Array.new
+ results = []
Chef::Search::Query.new.search(*args) do |o|
results << o
end
@@ -80,10 +80,24 @@ class Chef
raise
end
+ #
+ # Note that this is mixed into the Universal DSL so access to the node needs to be done
+ # through the run_context and not accessing the node method directly, since the node method
+ # is not as universal as the run_context.
+ #
+
+ # True if all the tags are set on the node.
+ #
+ # @param [Array<String>] tags to check against
+ # @return boolean
+ #
+ def tagged?(*tags)
+ tags.each do |tag|
+ return false unless run_context.node.tags.include?(tag)
+ end
+ true
+ end
+
end
end
end
-
-# **DEPRECATED**
-# This used to be part of chef/mixin/language. Load the file to activate the deprecation code.
-require "chef/mixin/language"
diff --git a/lib/chef/dsl/declare_resource.rb b/lib/chef/dsl/declare_resource.rb
index 86227a0f9d..8053098085 100644
--- a/lib/chef/dsl/declare_resource.rb
+++ b/lib/chef/dsl/declare_resource.rb
@@ -1,7 +1,7 @@
#--
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Walters
-# Copyright:: Copyright 2008-2016, 2009-2015 Chef Software, Inc.
+# Copyright:: Copyright (c) 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/exceptions"
+require_relative "../exceptions"
class Chef
module DSL
@@ -41,13 +41,14 @@ class Chef
#
def with_run_context(rc)
raise ArgumentError, "with_run_context is useless without a block" unless block_given?
+
old_run_context = @run_context
@run_context =
case rc
when Chef::RunContext
rc
when :root
- Chef.run_context
+ run_context.root_run_context
when :parent
run_context.parent_run_context
else
@@ -68,7 +69,7 @@ class Chef
# @return [Chef::Resource] The resource
#
# @example
- # delete_resource!(:template, '/x/y.txy')
+ # delete_resource!(:template, '/x/y.txt')
#
def delete_resource!(type, name, run_context: self.run_context)
run_context.resource_collection.delete("#{type}[#{name}]").tap do |resource|
@@ -92,7 +93,7 @@ class Chef
# @return [Chef::Resource] The resource
#
# @example
- # delete_resource(:template, '/x/y.txy')
+ # delete_resource(:template, '/x/y.txt')
#
def delete_resource(type, name, run_context: self.run_context)
delete_resource!(type, name, run_context: run_context)
@@ -113,13 +114,19 @@ class Chef
# @return [Chef::Resource] The updated resource
#
# @example
- # edit_resource!(:template, '/x/y.txy') do
+ # edit_resource!(:template, '/x/y.txt') do
# cookbook_name: cookbook_name
# end
#
- def edit_resource!(type, name, created_at = nil, run_context: self.run_context, &resource_attrs_block)
+ def edit_resource!(type, name, created_at: nil, run_context: self.run_context, &resource_attrs_block)
resource = find_resource!(type, name, run_context: run_context)
- resource.instance_eval(&resource_attrs_block) if block_given?
+ if resource_attrs_block
+ if defined?(new_resource)
+ resource.instance_exec(new_resource, &resource_attrs_block)
+ else
+ resource.instance_exec(&resource_attrs_block)
+ end
+ end
resource
end
@@ -140,16 +147,45 @@ class Chef
# @return [Chef::Resource] The updated or created resource
#
# @example
- # resource = edit_resource(:template, '/x/y.txy') do
- # source "y.txy.erb"
+ # resource = edit_resource(:template, '/x/y.txt') do
+ # source "y.txt.erb"
# variables {}
# end
- # resource.variables.merge!({ home: "/home/klowns" })
+ # resource.variables.merge!({ home: "/home/clowns" })
#
- def edit_resource(type, name, created_at = nil, run_context: self.run_context, &resource_attrs_block)
- edit_resource!(type, name, created_at, run_context: run_context, &resource_attrs_block)
+ def edit_resource(type, name, created_at: nil, run_context: self.run_context, &resource_attrs_block)
+ edit_resource!(type, name, created_at: created_at, run_context: run_context, &resource_attrs_block)
rescue Chef::Exceptions::ResourceNotFound
- declare_resource(type, name, created_at, run_context: run_context, &resource_attrs_block)
+ resource = declare_resource(type, name, created_at: created_at, run_context: run_context)
+ if resource_attrs_block
+ if defined?(new_resource)
+ resource.instance_exec(new_resource, &resource_attrs_block)
+ else
+ resource.instance_exec(&resource_attrs_block)
+ end
+ end
+ resource
+ end
+
+ # Find existing resources by searching the list of existing resources. Possible
+ # forms are:
+ #
+ # find(:file => "foobar")
+ # find(:file => [ "foobar", "baz" ])
+ # find("file[foobar]", "file[baz]")
+ # find("file[foobar,baz]")
+ #
+ # Calls `run_context.resource_collection.find(*args)`
+ #
+ # The is backcompat API, the use of find_resource, below, is encouraged.
+ #
+ # @return the matching resource, or an Array of matching resources.
+ #
+ # @raise ArgumentError if you feed it bad lookup information
+ # @raise RuntimeError if it can't find the resources you are looking for.
+ #
+ def resources(*args)
+ run_context.resource_collection.find(*args)
end
# Lookup a resource in the resource collection by name. If the resource is not
@@ -163,10 +199,11 @@ class Chef
# @return [Chef::Resource] The updated resource
#
# @example
- # resource = find_resource!(:template, '/x/y.txy')
+ # resource = find_resource!(:template, '/x/y.txt')
#
def find_resource!(type, name, run_context: self.run_context)
raise ArgumentError, "find_resource! does not take a block" if block_given?
+
run_context.resource_collection.find(type => name)
end
@@ -182,7 +219,7 @@ class Chef
# @return [Chef::Resource] The updated resource
#
# @example
- # if ( find_resource(:template, '/x/y.txy') )
+ # if ( find_resource(:template, '/x/y.txt') )
# # do something
# else
# # don't worry about the error
@@ -200,8 +237,8 @@ class Chef
def find_resource(type, name, created_at: nil, run_context: self.run_context, &resource_attrs_block)
find_resource!(type, name, run_context: run_context)
rescue Chef::Exceptions::ResourceNotFound
- if block_given?
- declare_resource(type, name, created_at, run_context: run_context, &resource_attrs_block)
+ if resource_attrs_block
+ declare_resource(type, name, created_at: created_at, run_context: run_context, &resource_attrs_block)
end # returns nil otherwise
end
@@ -222,7 +259,7 @@ class Chef
# @return [Chef::Resource] The new resource.
#
# @example
- # declare_resource(:file, '/x/y.txy', caller[0]) do
+ # declare_resource(:file, '/x/y.txt', caller[0]) do
# action :delete
# end
# # Equivalent to
@@ -230,18 +267,12 @@ class Chef
# action :delete
# end
#
- def declare_resource(type, name, created_at = nil, run_context: self.run_context, create_if_missing: false, &resource_attrs_block)
+ def declare_resource(type, name, created_at: nil, run_context: self.run_context, enclosing_provider: nil, &resource_attrs_block)
created_at ||= caller[0]
- if create_if_missing
- Chef::Log.deprecation "build_resource with a create_if_missing flag is deprecated, use edit_resource instead"
- # midly goofy since we call edit_resource only to re-call ourselves, but that's why its deprecated...
- return edit_resource(type, name, created_at, run_context: run_context, &resource_attrs_block)
- end
-
- resource = build_resource(type, name, created_at, &resource_attrs_block)
+ resource = build_resource(type, name, created_at: created_at, enclosing_provider: enclosing_provider, &resource_attrs_block)
- run_context.resource_collection.insert(resource, resource_type: type, instance_name: name)
+ run_context.resource_collection.insert(resource, resource_type: resource.declared_type, instance_name: resource.name)
resource
end
@@ -262,16 +293,18 @@ class Chef
# @return [Chef::Resource] The new resource.
#
# @example
- # build_resource(:file, '/x/y.txy', caller[0]) do
+ # build_resource(:file, '/x/y.txt', caller[0]) do
# action :delete
# end
#
- def build_resource(type, name, created_at = nil, run_context: self.run_context, &resource_attrs_block)
+ def build_resource(type, name, created_at: nil, run_context: self.run_context, enclosing_provider: nil, &resource_attrs_block)
created_at ||= caller[0]
# this needs to be lazy in order to avoid circular dependencies since ResourceBuilder
# will requires the entire provider+resolver universe
- require "chef/resource_builder" unless defined?(Chef::ResourceBuilder)
+ require_relative "../resource_builder" unless defined?(Chef::ResourceBuilder)
+
+ enclosing_provider ||= self if is_a?(Chef::Provider)
Chef::ResourceBuilder.new(
type: type,
@@ -281,9 +314,10 @@ class Chef
run_context: run_context,
cookbook_name: cookbook_name,
recipe_name: recipe_name,
- enclosing_provider: self.is_a?(Chef::Provider) ? self : nil
+ enclosing_provider: enclosing_provider
).build(&resource_attrs_block)
end
+
end
end
end
diff --git a/lib/chef/dsl/definitions.rb b/lib/chef/dsl/definitions.rb
index 60b1cf6f61..00f6d91739 100644
--- a/lib/chef/dsl/definitions.rb
+++ b/lib/chef/dsl/definitions.rb
@@ -18,7 +18,7 @@ class Chef
# @api private
def has_resource_definition?(name)
- run_context.definitions.has_key?(name)
+ run_context.definitions.key?(name)
end
# Processes the arguments and block as a resource definition.
diff --git a/lib/chef/dsl/include_attribute.rb b/lib/chef/dsl/include_attribute.rb
index 6d27fefc25..d924974069 100644
--- a/lib/chef/dsl/include_attribute.rb
+++ b/lib/chef/dsl/include_attribute.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/log"
+require_relative "../log"
class Chef
module DSL
@@ -31,9 +31,9 @@ class Chef
attr_file_specs.flatten.each do |attr_file_spec|
cookbook_name, attr_file = parse_attribute_file_spec(attr_file_spec)
if run_context.loaded_fully_qualified_attribute?(cookbook_name, attr_file)
- Chef::Log.debug("I am not loading attribute file #{cookbook_name}::#{attr_file}, because I have already seen it.")
+ Chef::Log.trace("I am not loading attribute file #{cookbook_name}::#{attr_file}, because I have already seen it.")
else
- Chef::Log.debug("Loading Attribute #{cookbook_name}::#{attr_file}")
+ Chef::Log.trace("Loading Attribute #{cookbook_name}::#{attr_file}")
run_context.loaded_attribute(cookbook_name, attr_file)
attr_file_path = run_context.resolve_attribute(cookbook_name, attr_file)
node.from_file(attr_file_path)
@@ -55,7 +55,3 @@ class Chef
end
end
end
-
-# **DEPRECATED**
-# This used to be part of chef/mixin/language_include_attribute. Load the file to activate the deprecation code.
-require "chef/mixin/language_include_attribute"
diff --git a/lib/chef/dsl/include_recipe.rb b/lib/chef/dsl/include_recipe.rb
index 9abd7d135b..e7ac982d02 100644
--- a/lib/chef/dsl/include_recipe.rb
+++ b/lib/chef/dsl/include_recipe.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/log"
+require_relative "../log"
class Chef
module DSL
@@ -29,16 +29,6 @@ class Chef
def load_recipe(recipe_name)
run_context.load_recipe(recipe_name, current_cookbook: cookbook_name)
end
-
- def require_recipe(*args)
- Chef::Log.warn("require_recipe is deprecated and will be removed in a future release, please use include_recipe")
- include_recipe(*args)
- end
-
end
end
end
-
-# **DEPRECATED**
-# This used to be part of chef/mixin/language_include_recipe. Load the file to activate the deprecation code.
-require "chef/mixin/language_include_recipe"
diff --git a/lib/chef/dsl/method_missing.rb b/lib/chef/dsl/method_missing.rb
deleted file mode 100644
index 0d7ded30f3..0000000000
--- a/lib/chef/dsl/method_missing.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-#--
-# Copyright:: Copyright 2008-2016, 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.
-#
-
-class Chef
- module DSL
- # @deprecated scheduled to die in a Chef 13 fire
- module MethodMissing
- def describe_self_for_error
- if respond_to?(:name)
- %Q{`#{self.class} "#{name}"'}
- elsif respond_to?(:recipe_name)
- %Q{`#{self.class} "#{recipe_name}"'}
- else
- to_s
- end
- 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
- end
- end
-end
diff --git a/lib/chef/dsl/platform_introspection.rb b/lib/chef/dsl/platform_introspection.rb
index a0c2d33967..49ec396cf2 100644
--- a/lib/chef/dsl/platform_introspection.rb
+++ b/lib/chef/dsl/platform_introspection.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,13 +16,17 @@
# limitations under the License.
#
+autoload :ChefUtils, "chef-utils"
+require_relative "../mixin/chef_utils_wiring" unless defined?(Chef::Mixin::ChefUtilsWiring)
+
class Chef
module DSL
-
# == Chef::DSL::PlatformIntrospection
# Provides the DSL for platform-dependent switch logic, such as
# #value_for_platform.
module PlatformIntrospection
+ include ChefUtils
+ include Chef::Mixin::ChefUtilsWiring
# Implementation class for determining platform dependent values
class PlatformDependentValue
@@ -68,41 +72,41 @@ class Chef
private
def match_versions(node)
- begin
- platform, version = node[:platform].to_s, node[:platform_version].to_s
- return nil unless @values.key?(platform)
- node_version = Chef::Version::Platform.new(version)
- key_matches = []
- keys = @values[platform].keys
- keys.each do |k|
- begin
- 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::Platform."
- Chef::Log.debug(e)
- end
- end
- return @values[platform][version] if key_matches.include?(version)
- case key_matches.length
- when 0
- return nil
- when 1
- return @values[platform][key_matches.first]
- else
- raise "Multiple matches detected for #{platform} with values #{@values}. The matches are: #{key_matches}"
+ platform, version = node[:platform].to_s, node[:platform_version].to_s
+ return nil unless @values.key?(platform)
+
+ node_version = Chef::Version::Platform.new(version)
+ key_matches = []
+ keys = @values[platform].keys
+ keys.each do |k|
+
+ if Chef::VersionConstraint::Platform.new(k).include?(node_version)
+ key_matches << k
end
- rescue Chef::Exceptions::InvalidCookbookVersion => e
- # Lets not break because someone passes a weird string like 'default' :)
- Chef::Log.debug(e)
- Chef::Log.debug "InvalidCookbookVersion exceptions are common and expected here: the generic constraint matcher attempted to match something which is not a constraint. Moving on to next version or constraint"
- return nil
- rescue Chef::Exceptions::InvalidPlatformVersion => e
- Chef::Log.debug "Caught InvalidPlatformVersion, this means that Chef::Version::Platform does not know how to turn #{node_version} into an x.y.z format"
- Chef::Log.debug(e)
- return nil
+ rescue Chef::Exceptions::InvalidVersionConstraint => e
+ Chef::Log.trace "Caught InvalidVersionConstraint. This means that a key in value_for_platform cannot be interpreted as a Chef::VersionConstraint::Platform."
+ Chef::Log.trace(e)
+
end
+ return @values[platform][version] if key_matches.include?(version)
+
+ case key_matches.length
+ when 0
+ nil
+ when 1
+ @values[platform][key_matches.first]
+ else
+ raise "Multiple matches detected for #{platform} with values #{@values}. The matches are: #{key_matches}"
+ end
+ rescue Chef::Exceptions::InvalidCookbookVersion => e
+ # Lets not break because someone passes a weird string like 'default' :)
+ Chef::Log.trace(e)
+ Chef::Log.trace "InvalidCookbookVersion exceptions are common and expected here: the generic constraint matcher attempted to match something which is not a constraint. Moving on to next version or constraint"
+ nil
+ rescue Chef::Exceptions::InvalidPlatformVersion => e
+ Chef::Log.trace "Caught InvalidPlatformVersion, this means that Chef::Version::Platform does not know how to turn #{node_version} into an x.y.z format"
+ Chef::Log.trace(e)
+ nil
end
def set(platforms, value)
@@ -126,7 +130,7 @@ class Chef
end
def assert_valid_platform_values!(platforms, value)
- unless value.kind_of?(Hash)
+ unless value.is_a?(Hash)
msg = "platform dependent values must be specified in the format :platform => {:version => value} "
msg << "you gave a value #{value.inspect} for platform(s) #{platforms}"
raise ArgumentError, msg
@@ -166,7 +170,7 @@ class Chef
has_platform
end
- # Implementation class for determining platform family dependent values
+ # Implementation class for determining platform family dependent values
class PlatformFamilyDependentValue
# Create a platform family dependent value object.
@@ -245,26 +249,17 @@ class Chef
end
end
- # Shamelessly stolen from https://github.com/sethvargo/chef-sugar/blob/master/lib/chef/sugar/docker.rb
- # Given a node object, returns whether the node is a docker container.
+ # a simple helper to determine if we're on a windows release pre-2012 / 8
#
- # === Parameters
- # node:: [Chef::Node] The node to check.
- #
- # === Returns
- # true:: if the current node is a docker container
- # false:: if the current node is not a docker container
- def docker?(node = run_context.nil? ? nil : run_context.node)
- # Using "File.exist?('/.dockerinit') || File.exist?('/.dockerenv')" makes Travis sad,
- # and that makes us sad too.
- node && node[:virtualization] && node[:virtualization][:systems] &&
- node[:virtualization][:systems][:docker] && node[:virtualization][:systems][:docker] == "guest"
+ # @deprecated Windows releases before Windows 2012 and 8 are no longer supported
+ # @return [Boolean] Is the system older than Windows 8 / 2012
+ def older_than_win_2012_or_8?(node = run_context.nil? ? nil : run_context.node)
+ false # we don't support platforms that would be true
end
+ # ^^^^^^ NOTE: PLEASE DO NOT CONTINUE TO ADD THESE KINDS OF PLATFORM_VERSION APIS WITHOUT ^^^^^^^
+ # ^^^^^^ GOING THROUGH THE DESIGN REVIEW PROCESS AND ADDRESS THE EXISTING CHEF-SUGAR ONES ^^^^^^^
+ # ^^^^^^ DO "THE HARD RIGHT THING" AND ADDRESS THE BROADER PROBLEM AND FIX IT ALL. ^^^^^^^
end
end
end
-
-# **DEPRECATED**
-# This used to be part of chef/mixin/language. Load the file to activate the deprecation code.
-require "chef/mixin/language"
diff --git a/lib/chef/dsl/powershell.rb b/lib/chef/dsl/powershell.rb
index 7dc7a9a0f6..d01efc0b2a 100644
--- a/lib/chef/dsl/powershell.rb
+++ b/lib/chef/dsl/powershell.rb
@@ -1,6 +1,6 @@
#
# Author:: Jay Mundrawala (<jdm@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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/util/powershell/ps_credential"
+require_relative "../util/powershell/ps_credential"
class Chef
module DSL
diff --git a/lib/chef/dsl/reboot_pending.rb b/lib/chef/dsl/reboot_pending.rb
index e8982d290e..19d3a6b0bd 100644
--- a/lib/chef/dsl/reboot_pending.rb
+++ b/lib/chef/dsl/reboot_pending.rb
@@ -1,6 +1,6 @@
# Author:: Bryan McLellan <btm@loftninjas.org>
# Author:: Seth Chisamore <schisamo@chef.io>
-# Copyright:: Copyright 2011-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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/dsl/platform_introspection"
-require "chef/dsl/registry_helper"
+require_relative "platform_introspection"
+require_relative "registry_helper"
class Chef
module DSL
@@ -30,28 +30,21 @@ class Chef
# Note that we will silently miss any other platform-specific reboot notices besides Windows+Ubuntu.
def reboot_pending?
# don't break when used as a mixin in contexts without #node (e.g. specs).
- if self.respond_to?(:node, true) && node.run_context.reboot_requested?
+ if respond_to?(:node, true) && node.run_context.reboot_requested?
true
elsif platform?("windows")
# PendingFileRenameOperations contains pairs (REG_MULTI_SZ) of filenames that cannot be updated
# 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
+ # 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\RebootPending') ||
-
- # The mere existence of the UpdateExeVolatile key should indicate a pending restart for certain updates
- # http://support.microsoft.com/kb/832475
- 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]))
+ # Vista + Server 2008 and newer may have reboots pending from CBS
+ registry_key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending')
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")
diff --git a/lib/chef/dsl/recipe.rb b/lib/chef/dsl/recipe.rb
index 1bb8f130af..ad85a9dd91 100644
--- a/lib/chef/dsl/recipe.rb
+++ b/lib/chef/dsl/recipe.rb
@@ -1,7 +1,7 @@
-#--
+#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
-# Copyright:: Copyright 2008-2016, 2009-2015 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,33 +17,24 @@
# limitations under the License.
#
-require "chef/exceptions"
-require "chef/dsl/resources"
-require "chef/dsl/definitions"
-require "chef/dsl/data_query"
-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/core"
-require "chef/dsl/method_missing"
-require "chef/mixin/lazy_module_include"
+require_relative "../exceptions"
+require_relative "resources"
+require_relative "definitions"
+require_relative "include_recipe"
+require_relative "reboot_pending"
+require_relative "universal"
+require_relative "declare_resource"
+require_relative "../mixin/notifying_block"
+require_relative "../mixin/lazy_module_include"
class Chef
module DSL
# Part of a family of DSL mixins.
#
- # Chef::DSL::Recipe mixes into Recipes and LWRP Providers.
- # - this does not target core chef resources and providers.
+ # Chef::DSL::Recipe mixes into Recipes and Providers.
# - this is restricted to recipe/resource/provider context where a resource collection exists.
# - cookbook authors should typically include modules into here.
#
- # Chef::DSL::Core mixes into Recipes, LWRP Providers and Core Providers
- # - this adds cores providers on top of the Recipe DSL.
- # - this is restricted to recipe/resource/provider context where a resource collection exists.
- # - core chef authors should typically include modules into here.
- #
# Chef::DSL::Universal mixes into Recipes, LWRP Resources+Providers, Core Resources+Providers, and Attributes files.
# - this adds resources and attributes files.
# - do not add helpers which manipulate the resource collection.
@@ -51,17 +42,13 @@ class Chef
# - it also pollutes the namespace of nearly every context, watch out.
#
module Recipe
- include Chef::DSL::Core
- include Chef::DSL::DataQuery
+ include Chef::DSL::Universal
+ include Chef::DSL::DeclareResource
+ include Chef::Mixin::NotifyingBlock
include Chef::DSL::IncludeRecipe
- include Chef::DSL::RegistryHelper
include Chef::DSL::RebootPending
- include Chef::DSL::Audit
- include Chef::DSL::Powershell
include Chef::DSL::Resources
include Chef::DSL::Definitions
- # method_missing will disappear in Chef 13
- include Chef::DSL::MethodMissing
extend Chef::Mixin::LazyModuleInclude
def resource_class_for(snake_case_name)
@@ -77,19 +64,6 @@ class Chef
def exec(args)
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 Use Chef::DSL::Recipe instead, will be removed in Chef 13
- module FullDSL
- include Chef::DSL::Recipe
- extend Chef::Mixin::LazyModuleInclude
- end
end
end
end
-
-# 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"
diff --git a/lib/chef/dsl/registry_helper.rb b/lib/chef/dsl/registry_helper.rb
index 63da635ef1..c1ebdf68e2 100644
--- a/lib/chef/dsl/registry_helper.rb
+++ b/lib/chef/dsl/registry_helper.rb
@@ -1,6 +1,6 @@
#
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/lib/chef/dsl/resources.rb b/lib/chef/dsl/resources.rb
index 7bbfeb2914..190d5be71a 100644
--- a/lib/chef/dsl/resources.rb
+++ b/lib/chef/dsl/resources.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser <jkeiser@chef.io>
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,8 +15,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require "chef/dsl/cheffish"
-require "chef/dsl/chef_provisioning"
+require_relative "cheffish"
class Chef
module DSL
@@ -27,26 +26,16 @@ class Chef
#
# @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 the lazy loader for cheffish, so that the
+ # resource DSL is there but the gem isn'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)
+ module_eval(<<-EOM, __FILE__, __LINE__ + 1)
+ def #{dsl_name}(args = nil, &block)
+ declare_resource(#{dsl_name.inspect}, args, created_at: 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
+ EOM
end
def self.remove_resource_dsl(dsl_name)
diff --git a/lib/chef/dsl/universal.rb b/lib/chef/dsl/universal.rb
index 810792f542..1e4e4218d8 100644
--- a/lib/chef/dsl/universal.rb
+++ b/lib/chef/dsl/universal.rb
@@ -1,7 +1,7 @@
#--
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
-# Copyright:: Copyright 2008-2016, 2009-2015 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,24 +17,24 @@
# limitations under the License.
#
-require "chef/dsl/platform_introspection"
-require "chef/mixin/powershell_out"
-require "chef/mixin/shell_out"
+require_relative "platform_introspection"
+require_relative "data_query"
+require_relative "chef_vault"
+require_relative "registry_helper"
+require_relative "powershell"
+require_relative "../mixin/powershell_exec"
+require_relative "../mixin/powershell_out"
+require_relative "../mixin/shell_out"
+require_relative "../mixin/lazy_module_include"
class Chef
module DSL
# Part of a family of DSL mixins.
#
- # Chef::DSL::Recipe mixes into Recipes and LWRP Providers.
- # - this does not target core chef resources and providers.
+ # Chef::DSL::Recipe mixes into Recipes and Providers.
# - this is restricted to recipe/resource/provider context where a resource collection exists.
# - cookbook authors should typically include modules into here.
#
- # Chef::DSL::Core mixes into Recipes, LWRP Providers and Core Providers
- # - this adds cores providers on top of the Recipe DSL.
- # - this is restricted to recipe/resource/provider context where a resource collection exists.
- # - core chef authors should typically include modules into here.
- #
# Chef::DSL::Universal mixes into Recipes, LWRP Resources+Providers, Core Resources+Providers, and Attributes files.
# - this adds resources and attributes files.
# - do not add helpers which manipulate the resource collection.
@@ -43,8 +43,14 @@ class Chef
#
module Universal
include Chef::DSL::PlatformIntrospection
+ include Chef::DSL::DataQuery
+ include Chef::DSL::ChefVault
+ include Chef::DSL::RegistryHelper
+ include Chef::DSL::Powershell
+ include Chef::Mixin::PowershellExec
include Chef::Mixin::PowershellOut
include Chef::Mixin::ShellOut
+ extend Chef::Mixin::LazyModuleInclude
end
end
end
diff --git a/lib/chef/encrypted_data_bag_item.rb b/lib/chef/encrypted_data_bag_item.rb
index e696199c63..9cf1a71db2 100644
--- a/lib/chef/encrypted_data_bag_item.rb
+++ b/lib/chef/encrypted_data_bag_item.rb
@@ -1,6 +1,6 @@
#
# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,11 +16,10 @@
# 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_relative "config"
+Chef.autoload :DataBagItem, File.expand_path("data_bag_item", __dir__)
+require_relative "encrypted_data_bag_item/decryptor"
+require_relative "encrypted_data_bag_item/encryptor"
# An EncryptedDataBagItem represents a read-only data bag item where
# all values, except for the value associated with the id key, have
@@ -47,8 +46,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".freeze
+ AEAD_ALGORITHM = "aes-256-gcm".freeze
#
# === Synopsis
@@ -84,10 +83,12 @@ class Chef::EncryptedDataBagItem
raise ArgumentError, "assignment not supported for #{self.class}"
end
- def to_hash
+ def to_h
@enc_hash.keys.inject({}) { |hash, key| hash[key] = self[key]; hash }
end
+ alias_method :to_hash, :to_h
+
def self.encrypt_data_bag_item(plain_hash, secret)
plain_hash.inject({}) do |h, (key, val)|
h[key] = if key != "id"
@@ -121,17 +122,19 @@ class Chef::EncryptedDataBagItem
#
def self.load(data_bag, name, secret = nil)
raw_hash = Chef::DataBagItem.load(data_bag, name)
- secret = secret || self.load_secret
- self.new(raw_hash, secret)
+ secret ||= load_secret
+ new(raw_hash, secret)
end
def self.load_secret(path = nil)
+ require "open-uri" unless defined?(OpenURI)
path ||= Chef::Config[:encrypted_data_bag_secret]
- if !path
- raise ArgumentError, "No secret specified and no secret found at #{Chef::Config.platform_specific_path('/etc/chef/encrypted_data_bag_secret')}"
+ unless path
+ raise ArgumentError, "No secret specified and no secret found at #{Chef::Config.platform_specific_path(ChefConfig::Config.etc_chef_dir) + "/encrypted_data_bag_secret"}"
end
+
secret = case path
- when /^\w+:\/\//
+ when %r{^\w+://}
# We have a remote key
begin
Kernel.open(path).read.strip
@@ -141,14 +144,16 @@ class Chef::EncryptedDataBagItem
raise ArgumentError, "Remote key not found at '#{path}'"
end
else
- if !File.exist?(path)
+ unless File.exist?(path)
raise Errno::ENOENT, "file not found '#{path}'"
end
+
IO.read(path).strip
end
if secret.size < 1
raise ArgumentError, "invalid zero length secret in '#{path}'"
end
+
secret
end
diff --git a/lib/chef/encrypted_data_bag_item/assertions.rb b/lib/chef/encrypted_data_bag_item/assertions.rb
index e8f8bfcbf2..13ed0de050 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_relative "unacceptable_encrypted_data_bag_item_format"
+require_relative "unsupported_cipher"
class Chef::EncryptedDataBagItem
@@ -27,10 +27,10 @@ class Chef::EncryptedDataBagItem
module Assertions
def assert_format_version_acceptable!(format_version)
- unless format_version.kind_of?(Integer) && format_version >= Chef::Config[:data_bag_decrypt_minimum_version]
+ unless format_version.is_a?(Integer) && format_version >= Chef::Config[:data_bag_decrypt_minimum_version]
raise UnacceptableEncryptedDataBagItemFormat,
"The encrypted data bag item has format version `#{format_version}', " +
- "but the config setting 'data_bag_decrypt_minimum_version' requires version `#{Chef::Config[:data_bag_decrypt_minimum_version]}'"
+ "but the config setting 'data_bag_decrypt_minimum_version' requires version `#{Chef::Config[:data_bag_decrypt_minimum_version]}'"
end
end
diff --git a/lib/chef/encrypted_data_bag_item/check_encrypted.rb b/lib/chef/encrypted_data_bag_item/check_encrypted.rb
index cc378194ff..fc3a232fc8 100644
--- a/lib/chef/encrypted_data_bag_item/check_encrypted.rb
+++ b/lib/chef/encrypted_data_bag_item/check_encrypted.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Ball (<tball@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/encrypted_data_bag_item/encryptor"
+require_relative "encryptor"
class Chef::EncryptedDataBagItem
# Common code for checking if a data bag appears encrypted
@@ -39,7 +39,8 @@ class Chef::EncryptedDataBagItem
# true only when there is an exact match between the VersionXEncryptor
# keys and the hash's keys.
def looks_like_encrypted?(data)
- return false unless data.is_a?(Hash) && data.has_key?("version")
+ return false unless data.is_a?(Hash) && data.key?("version")
+
case data["version"]
when 1
Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor.encryptor_keys.sort == data.keys.sort
diff --git a/lib/chef/encrypted_data_bag_item/decryption_failure.rb b/lib/chef/encrypted_data_bag_item/decryption_failure.rb
index 9d2d998057..5a57be9934 100644
--- a/lib/chef/encrypted_data_bag_item/decryption_failure.rb
+++ b/lib/chef/encrypted_data_bag_item/decryption_failure.rb
@@ -1,6 +1,6 @@
#
# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/lib/chef/encrypted_data_bag_item/decryptor.rb b/lib/chef/encrypted_data_bag_item/decryptor.rb
index 773ff4e154..57119796a9 100644
--- a/lib/chef/encrypted_data_bag_item/decryptor.rb
+++ b/lib/chef/encrypted_data_bag_item/decryptor.rb
@@ -1,6 +1,6 @@
#
# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -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"
+autoload :YAML, "yaml"
+require_relative "../json_compat"
+autoload :OpenSSL, "openssl"
+autoload :Base64, "base64"
+require "digest/sha2" unless defined?(Digest::SHA2)
+require_relative "../encrypted_data_bag_item"
+require_relative "unsupported_encrypted_data_bag_item_format"
+require_relative "decryption_failure"
+require_relative "assertions"
class Chef::EncryptedDataBagItem
@@ -88,13 +88,14 @@ class Chef::EncryptedDataBagItem
end
def decrypted_data
- @decrypted_data ||= begin
- plaintext = openssl_decryptor.update(encrypted_bytes)
- plaintext << openssl_decryptor.final
- rescue OpenSSL::Cipher::CipherError => e
- # if the key length is less than 255 characters, and it contains slashes, we think it may be a path.
- raise DecryptionFailure, "Error decrypting data bag value: '#{e.message}'. Most likely the provided key is incorrect. #{ (@key.length < 255 && @key.include?('/')) ? 'You may need to use --secret-file rather than --secret.' : '' }"
- end
+ @decrypted_data ||=
+ begin
+ plaintext = openssl_decryptor.update(encrypted_bytes)
+ plaintext << openssl_decryptor.final
+ rescue OpenSSL::Cipher::CipherError => e
+ # if the key length is less than 255 characters, and it contains slashes, we think it may be a path.
+ raise DecryptionFailure, "Error decrypting data bag value: '#{e.message}'. Most likely the provided key is incorrect. #{(@key.length < 255 && @key.include?("/")) ? "You may need to use --secret-file rather than --secret." : ""}"
+ end
end
def encrypted_bytes
@@ -102,12 +103,13 @@ class Chef::EncryptedDataBagItem
end
def openssl_decryptor
- @openssl_decryptor ||= begin
- d = OpenSSL::Cipher.new(algorithm)
- d.decrypt
- d.pkcs5_keyivgen(key)
- d
- end
+ @openssl_decryptor ||=
+ begin
+ d = OpenSSL::Cipher.new(algorithm)
+ d.decrypt
+ d.pkcs5_keyivgen(key)
+ d
+ end
end
end
@@ -139,25 +141,27 @@ class Chef::EncryptedDataBagItem
end
def decrypted_data
- @decrypted_data ||= begin
- plaintext = openssl_decryptor.update(encrypted_bytes)
- plaintext << openssl_decryptor.final
- rescue OpenSSL::Cipher::CipherError => e
- # if the key length is less than 255 characters, and it contains slashes, we think it may be a path.
- raise DecryptionFailure, "Error decrypting data bag value: '#{e.message}'. Most likely the provided key is incorrect. #{ ( @key.length < 255 && @key.include?('/')) ? 'You may need to use --secret-file rather than --secret.' : '' }"
- end
+ @decrypted_data ||=
+ begin
+ plaintext = openssl_decryptor.update(encrypted_bytes)
+ plaintext << openssl_decryptor.final
+ rescue OpenSSL::Cipher::CipherError => e
+ # if the key length is less than 255 characters, and it contains slashes, we think it may be a path.
+ raise DecryptionFailure, "Error decrypting data bag value: '#{e.message}'. Most likely the provided key is incorrect. #{( @key.length < 255 && @key.include?("/")) ? "You may need to use --secret-file rather than --secret." : ""}"
+ end
end
def openssl_decryptor
- @openssl_decryptor ||= begin
- assert_valid_cipher!(@encrypted_data["cipher"], algorithm)
- d = OpenSSL::Cipher.new(algorithm)
- d.decrypt
- # We must set key before iv: https://bugs.ruby-lang.org/issues/8221
- d.key = OpenSSL::Digest::SHA256.digest(key)
- d.iv = iv
- d
- end
+ @openssl_decryptor ||=
+ begin
+ assert_valid_cipher!(@encrypted_data["cipher"], algorithm)
+ d = OpenSSL::Cipher.new(algorithm)
+ d.decrypt
+ # We must set key before iv: https://bugs.ruby-lang.org/issues/8221
+ d.key = OpenSSL::Digest.digest("SHA256", key)
+ d.iv = iv
+ d
+ end
end
end
@@ -184,6 +188,7 @@ class Chef::EncryptedDataBagItem
def candidate_hmac_matches?(expected_hmac)
return false unless @encrypted_data["hmac"]
+
expected_bytes = expected_hmac.bytes.to_a
candidate_hmac_bytes = Base64.decode64(@encrypted_data["hmac"]).bytes.to_a
valid = expected_bytes.size ^ candidate_hmac_bytes.size
@@ -209,16 +214,18 @@ class Chef::EncryptedDataBagItem
if auth_tag_b64.nil?
raise DecryptionFailure, "Error decrypting data bag value: invalid authentication tag. Most likely the data is corrupted"
end
+
Base64.decode64(auth_tag_b64)
end
def openssl_decryptor
- @openssl_decryptor ||= begin
- d = super
- d.auth_tag = auth_tag
- d.auth_data = ""
- d
- end
+ @openssl_decryptor ||=
+ begin
+ d = super
+ d.auth_tag = auth_tag
+ d.auth_data = ""
+ d
+ end
end
end
diff --git a/lib/chef/encrypted_data_bag_item/encryptor.rb b/lib/chef/encrypted_data_bag_item/encryptor.rb
index 8d34033db6..f068d134d8 100644
--- a/lib/chef/encrypted_data_bag_item/encryptor.rb
+++ b/lib/chef/encrypted_data_bag_item/encryptor.rb
@@ -1,6 +1,6 @@
#
# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -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"
+autoload :Base64, "base64"
+require "digest/sha2" unless defined?(Digest::SHA2)
+autoload :OpenSSL, "openssl"
+autoload :FFI_Yajl, "ffi_yajl"
+require_relative "../encrypted_data_bag_item"
+require_relative "unsupported_encrypted_data_bag_item_format"
+require_relative "encryption_failure"
+require_relative "assertions"
class Chef::EncryptedDataBagItem
@@ -102,7 +102,7 @@ class Chef::EncryptedDataBagItem
encryptor = OpenSSL::Cipher.new(algorithm)
encryptor.encrypt
# We must set key before iv: https://bugs.ruby-lang.org/issues/8221
- encryptor.key = OpenSSL::Digest::SHA256.digest(key)
+ encryptor.key = OpenSSL::Digest.digest("SHA256", key)
@iv ||= encryptor.random_iv
encryptor.iv = @iv
encryptor
@@ -123,7 +123,7 @@ class Chef::EncryptedDataBagItem
# Strings) that do not produce valid JSON when serialized without the
# wrapper.
def serialized_data
- FFI_Yajl::Encoder.encode(:json_wrapper => plaintext_data)
+ FFI_Yajl::Encoder.encode(json_wrapper: plaintext_data)
end
def self.encryptor_keys
@@ -193,6 +193,7 @@ class Chef::EncryptedDataBagItem
if @auth_tag.nil?
raise EncryptionFailure, "Internal Error: GCM authentication tag read before encryption"
end
+
@auth_tag
end
diff --git a/lib/chef/encrypted_data_bag_item/unacceptable_encrypted_data_bag_item_format.rb b/lib/chef/encrypted_data_bag_item/unacceptable_encrypted_data_bag_item_format.rb
index 362954b24d..89e03ea6d8 100644
--- a/lib/chef/encrypted_data_bag_item/unacceptable_encrypted_data_bag_item_format.rb
+++ b/lib/chef/encrypted_data_bag_item/unacceptable_encrypted_data_bag_item_format.rb
@@ -1,6 +1,6 @@
#
# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/lib/chef/encrypted_data_bag_item/unsupported_cipher.rb b/lib/chef/encrypted_data_bag_item/unsupported_cipher.rb
index 6f1221bb68..228043e526 100644
--- a/lib/chef/encrypted_data_bag_item/unsupported_cipher.rb
+++ b/lib/chef/encrypted_data_bag_item/unsupported_cipher.rb
@@ -1,6 +1,6 @@
#
# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/lib/chef/encrypted_data_bag_item/unsupported_encrypted_data_bag_item_format.rb b/lib/chef/encrypted_data_bag_item/unsupported_encrypted_data_bag_item_format.rb
index ea464636db..ed6e6bd850 100644
--- a/lib/chef/encrypted_data_bag_item/unsupported_encrypted_data_bag_item_format.rb
+++ b/lib/chef/encrypted_data_bag_item/unsupported_encrypted_data_bag_item_format.rb
@@ -1,6 +1,6 @@
#
# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/lib/chef/environment.rb b/lib/chef/environment.rb
index 32ebde6f4f..e651e1b4aa 100644
--- a/lib/chef/environment.rb
+++ b/lib/chef/environment.rb
@@ -3,7 +3,7 @@
# Author:: Seth Falcon (<seth@chef.io>)
# Author:: John Keiser (<jkeiser@ospcode.com>)
# Author:: Kyle Goodwin (<kgoodwin@primerevenue.com>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,31 +19,30 @@
# 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/server_api"
+require_relative "config"
+require_relative "mash"
+require_relative "mixin/params_validate"
+require_relative "mixin/from_file"
+require_relative "version_constraint"
+require_relative "server_api"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Environment
- DEFAULT = "default"
+ DEFAULT = "default".freeze
include Chef::Mixin::ParamsValidate
include Chef::Mixin::FromFile
- attr_accessor :chef_server_rest
-
- COMBINED_COOKBOOK_CONSTRAINT = /(.+)(?:[\s]+)((?:#{Chef::VersionConstraint::OPS.join('|')})(?:[\s]+).+)$/
+ COMBINED_COOKBOOK_CONSTRAINT = /(.+)(?:\s+)((?:#{Chef::VersionConstraint::OPS.join('|')})(?:\s+).+)$/.freeze
def initialize(chef_server_rest: nil)
@name = ""
@description = ""
@default_attributes = Mash.new
@override_attributes = Mash.new
- @cookbook_versions = Hash.new
+ @cookbook_versions = {}
@chef_server_rest = chef_server_rest
end
@@ -59,7 +58,7 @@ class Chef
set_or_return(
:name,
arg,
- { :regex => /^[\-[:alnum:]_]+$/, :kind_of => String }
+ { regex: /^[\-[:alnum:]_]+$/, kind_of: String }
)
end
@@ -67,7 +66,7 @@ class Chef
set_or_return(
:description,
arg,
- :kind_of => String
+ kind_of: String
)
end
@@ -75,7 +74,7 @@ class Chef
set_or_return(
:default_attributes,
arg,
- :kind_of => Hash
+ kind_of: Hash
)
end
@@ -87,7 +86,7 @@ class Chef
set_or_return(
:override_attributes,
arg,
- :kind_of => Hash
+ kind_of: Hash
)
end
@@ -100,8 +99,8 @@ class Chef
:cookbook_versions,
arg,
{
- :kind_of => Hash,
- :callbacks => {
+ kind_of: Hash,
+ callbacks: {
"should be a valid set of cookbook version requirements" => lambda { |cv| Chef::Environment.validate_cookbook_versions(cv) },
},
}
@@ -110,17 +109,17 @@ class Chef
def cookbook(cookbook, version)
validate({
- :version => version,
+ version: version,
}, {
- :version => {
- :callbacks => { "should be a valid version requirement" => lambda { |v| Chef::Environment.validate_cookbook_version(v) } },
+ version: {
+ callbacks: { "should be a valid version requirement" => lambda { |v| Chef::Environment.validate_cookbook_version(v) } },
},
})
@cookbook_versions[cookbook] = version
end
- def to_hash
- result = {
+ def to_h
+ {
"name" => @name,
"description" => @description,
"cookbook_versions" => @cookbook_versions,
@@ -129,11 +128,12 @@ class Chef
"default_attributes" => @default_attributes,
"override_attributes" => @override_attributes,
}
- result
end
+ alias_method :to_hash, :to_h
+
def to_json(*a)
- Chef::JSONCompat.to_json(to_hash, *a)
+ Chef::JSONCompat.to_json(to_h, *a)
end
def update_from!(o)
@@ -157,7 +157,7 @@ class Chef
# reset because everything we need will be in the params, this is necessary because certain constraints
# may have been removed in the params and need to be removed from cookbook_versions as well.
bkup_cb_versions = cookbook_versions
- cookbook_versions(Hash.new)
+ cookbook_versions({})
valid = true
begin
@@ -171,7 +171,7 @@ class Chef
unless params[:cookbook_version].nil?
params[:cookbook_version].each do |index, cookbook_constraint_spec|
unless cookbook_constraint_spec.nil? || cookbook_constraint_spec.size == 0
- valid = valid && update_cookbook_constraint_from_param(index, cookbook_constraint_spec)
+ valid &&= update_cookbook_constraint_from_param(index, cookbook_constraint_spec)
end
end
end
@@ -216,11 +216,6 @@ class Chef
end
end
- def self.json_create(o)
- Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::Environment#from_hash")
- from_hash(o)
- end
-
def self.from_hash(o)
environment = new
environment.name(o["name"])
@@ -233,7 +228,7 @@ class Chef
def self.list(inflate = false)
if inflate
- response = Hash.new
+ response = {}
Chef::Search::Query.new.search(:environment) do |e|
response[e.name] = e unless e.nil?
end
@@ -247,7 +242,7 @@ class Chef
if Chef::Config[:solo_legacy_mode]
load_from_file(name)
else
- self.from_hash(chef_server_rest.get("environments/#{name}"))
+ from_hash(chef_server_rest.get("environments/#{name}"))
end
end
@@ -259,11 +254,11 @@ class Chef
js_file = File.join(Chef::Config[:environment_path], "#{name}.json")
rb_file = File.join(Chef::Config[:environment_path], "#{name}.rb")
- if File.exists?(js_file)
+ if File.exist?(js_file)
# from_json returns object.class => json_class in the JSON.
hash = Chef::JSONCompat.parse(IO.read(js_file))
from_hash(hash)
- elsif File.exists?(rb_file)
+ elsif File.exist?(rb_file)
environment = Chef::Environment.new
environment.name(name)
environment.from_file(rb_file)
@@ -280,8 +275,9 @@ class Chef
def save
begin
chef_server_rest.put("environments/#{@name}", self)
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => e
raise e unless e.response.code == "404"
+
chef_server_rest.post("environments", self)
end
self
@@ -301,25 +297,24 @@ class Chef
end
def self.validate_cookbook_versions(cv)
- return false unless cv.kind_of?(Hash)
- cv.each do |cookbook, version|
+ return false unless cv.is_a?(Hash)
+
+ cv.each_value do |version|
return false unless Chef::Environment.validate_cookbook_version(version)
end
true
end
def self.validate_cookbook_version(version)
- begin
- if Chef::Config[:solo_legacy_mode]
- raise Chef::Exceptions::IllegalVersionConstraint,
- "Environment cookbook version constraints not allowed in chef-solo"
- else
- Chef::VersionConstraint.new version
- true
- end
- rescue ArgumentError
- false
+ if Chef::Config[:solo_legacy_mode]
+ raise Chef::Exceptions::IllegalVersionConstraint,
+ "Environment cookbook version constraints not allowed in #{ChefUtils::Dist::Solo::PRODUCT}"
+ else
+ Chef::VersionConstraint.new version
+ true
end
+ rescue ArgumentError
+ false
end
end
diff --git a/lib/chef/event_dispatch/base.rb b/lib/chef/event_dispatch/base.rb
index 04c960c7af..f7b706cb2c 100644
--- a/lib/chef/event_dispatch/base.rb
+++ b/lib/chef/event_dispatch/base.rb
@@ -29,266 +29,206 @@ class Chef
class Base
# Called at the very start of a Chef Run
- def run_start(version)
- end
+ def run_start(version, run_status); end
- def run_started(run_status)
- end
+ def run_started(run_status); end
# Called at the end a successful Chef run.
- def run_completed(node)
- end
+ def run_completed(node, run_status); end
# Called at the end of a failed Chef run.
- def run_failed(exception)
- end
+ def run_failed(exception, run_status); end
# Called right after ohai runs.
- def ohai_completed(node)
- end
+ # NOTE: the node object here is always nil because of when it is called
+ def ohai_completed(node); end
# 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
+ def skipping_registration(node_name, config); end
# About to attempt to create a private key registered to the server with
# client +node_name+.
- def registration_start(node_name, config)
- end
+ def registration_start(node_name, config); end
# Successfully created the private key and registered this client with the
# server.
- def registration_completed
- end
+ def registration_completed; end
# Failed to register this client with the server.
- def registration_failed(node_name, exception, config)
- end
+ def registration_failed(node_name, exception, config); end
# Called before Chef client loads the node data from the server
- def node_load_start(node_name, config)
- end
+ def node_load_start(node_name, config); end
# TODO: def node_run_list_overridden(*args)
+ # Called once the node is loaded by the policy builder
+ def node_load_success(node); end
+
# Failed to load node data from the server
- def node_load_failed(node_name, exception, config)
- end
+ def node_load_failed(node_name, exception, config); end
# Error expanding the run list
- def run_list_expand_failed(node, exception)
- end
+ def run_list_expand_failed(node, exception); end
# Called after Chef client has loaded the node data.
# Default and override attrs from roles have been computed, but not yet applied.
# Normal attrs from JSON have been added to the node.
- def node_load_completed(node, expanded_run_list, config)
- end
+ 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
+ def policyfile_loaded(policy); end
# Called before the cookbook collection is fetched from the server.
- def cookbook_resolution_start(expanded_run_list)
- end
+ def cookbook_resolution_start(expanded_run_list); end
# Called when there is an error getting the cookbook collection from the
# server.
- def cookbook_resolution_failed(expanded_run_list, exception)
- end
+ def cookbook_resolution_failed(expanded_run_list, exception); end
# Called when the cookbook collection is returned from the server.
- def cookbook_resolution_complete(cookbook_collection)
- end
+ def cookbook_resolution_complete(cookbook_collection); end
# Called before unneeded cookbooks are removed
- def cookbook_clean_start
- end
+ def cookbook_clean_start; end
# Called after the file at +path+ is removed. It may be removed if the
# cookbook containing it was removed from the run list, or if the file was
# removed from the cookbook.
- def removed_cookbook_file(path)
- end
+ def removed_cookbook_file(path); end
# Called when cookbook cleaning is finished.
- def cookbook_clean_complete
- end
+ def cookbook_clean_complete; end
# Called before cookbook sync starts
- def cookbook_sync_start(cookbook_count)
- end
+ def cookbook_sync_start(cookbook_count); end
# Called when cookbook +cookbook+ has been sync'd
- def synchronized_cookbook(cookbook_name, cookbook)
- end
+ def synchronized_cookbook(cookbook_name, cookbook); end
# Called when an individual file in a cookbook has been updated
- def updated_cookbook_file(cookbook_name, path)
- end
+ def updated_cookbook_file(cookbook_name, path); end
# Called when an error occurs during cookbook sync
- def cookbook_sync_failed(cookbooks, exception)
- end
+ def cookbook_sync_failed(cookbooks, exception); end
# Called after all cookbooks have been sync'd.
- def cookbook_sync_complete
- end
+ def cookbook_sync_complete; end
# Called when starting to collect gems from the cookbooks
- def cookbook_gem_start(gems)
- end
+ def cookbook_gem_start(gems); end
# Called when the result of installing the bundle is to install the gem
- def cookbook_gem_installing(gem, version)
- end
+ def cookbook_gem_installing(gem, version); end
# Called when the result of installing the bundle is to use the gem
- def cookbook_gem_using(gem, version)
- end
+ def cookbook_gem_using(gem, version); end
# Called when finished installing cookbook gems
- def cookbook_gem_finished
- end
+ def cookbook_gem_finished; end
# Called when cookbook gem installation fails
- def cookbook_gem_failed(exception)
- end
+ def cookbook_gem_failed(exception); end
## TODO: add cookbook name to the API for file load callbacks
## TODO: add callbacks for overall cookbook eval start and complete.
+ # Called immediately after creating the run_context and before any cookbook compilation happens
+ def cookbook_compilation_start(run_context); end
+
# Called when library file loading starts
- def library_load_start(file_count)
- end
+ def library_load_start(file_count); end
# Called when library file has been loaded
- def library_file_loaded(path)
- end
+ def library_file_loaded(path); end
# Called when a library file has an error on load.
- def library_file_load_failed(path, exception)
- end
+ def library_file_load_failed(path, exception); end
# Called when library file loading has finished
- def library_load_complete
- end
+ def library_load_complete; end
# Called when LWRP loading starts
- def lwrp_load_start(lwrp_file_count)
- end
+ def lwrp_load_start(lwrp_file_count); end
# Called after a LWR or LWP has been loaded
- def lwrp_file_loaded(path)
- end
+ def lwrp_file_loaded(path); end
# Called after a LWR or LWP file errors on load
- def lwrp_file_load_failed(path, exception)
- end
+ def lwrp_file_load_failed(path, exception); end
# Called when LWRPs are finished loading
- def lwrp_load_complete
- end
+ def lwrp_load_complete; end
+
+ # Called when an ohai plugin file loading starts
+ def ohai_plugin_load_start(file_count); end
+
+ # Called when an ohai plugin file has been loaded
+ def ohai_plugin_file_loaded(path); end
+
+ # Called when an ohai plugin file has an error on load.
+ def ohai_plugin_file_load_failed(path, exception); end
+
+ # Called when an ohai plugin file loading has finished
+ def ohai_plugin_load_complete; end
# Called before attribute files are loaded
- def attribute_load_start(attribute_file_count)
- end
+ def attribute_load_start(attribute_file_count); end
# Called after the attribute file is loaded
- def attribute_file_loaded(path)
- end
+ def attribute_file_loaded(path); end
# Called when an attribute file fails to load.
- def attribute_file_load_failed(path, exception)
- end
+ def attribute_file_load_failed(path, exception); end
# Called when attribute file loading is finished
- def attribute_load_complete
- end
+ def attribute_load_complete; end
# Called before resource definitions are loaded
- def definition_load_start(definition_file_count)
- end
+ def definition_load_start(definition_file_count); end
# Called when a resource definition has been loaded
- def definition_file_loaded(path)
- end
+ def definition_file_loaded(path); end
# Called when a resource definition file fails to load
- def definition_file_load_failed(path, exception)
- end
+ def definition_file_load_failed(path, exception); end
# Called when resource definitions are done loading
- def definition_load_complete
- end
+ def definition_load_complete; end
# Called before recipes are loaded
- def recipe_load_start(recipe_count)
- end
+ def recipe_load_start(recipe_count); end
# Called after the recipe has been loaded
- def recipe_file_loaded(path, recipe)
- end
+ def recipe_file_loaded(path, recipe); end
# Called after a recipe file fails to load
- def recipe_file_load_failed(path, exception, recipe)
- end
+ def recipe_file_load_failed(path, exception, recipe); end
# Called when a recipe cannot be resolved
- def recipe_not_found(exception)
- end
+ def recipe_not_found(exception); end
# Called when recipes have been loaded.
- def recipe_load_complete
- end
+ def recipe_load_complete; end
+
+ # This is called after all cookbook compilation phases are completed.
+ def cookbook_compilation_complete(run_context); end
# Called before convergence starts
- def converge_start(run_context)
- end
+ def converge_start(run_context); end
+
+ # Callback hook for handlers to register their interest in the action_collection
+ def action_collection_registration(action_collection); end
# Called when the converge phase is finished.
- def converge_complete
- end
+ def converge_complete; end
# Called if the converge phase fails
- def converge_failed(exception)
- end
-
- ##################################
- # Audit Mode Events
- # This phase is currently experimental and these event APIs are subject to change
- ##################################
-
- # Called before audit phase starts
- def audit_phase_start(run_status)
- end
-
- # Called when audit phase successfully finishes
- 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, audit_output)
- end
-
- # Signifies the start of a `control_group` block with a defined name
- def control_group_started(name)
- end
-
- # An example in a `control_group` block completed successfully
- def control_example_success(control_group_name, example_data)
- end
-
- # An example in a `control_group` block failed with the provided error
- def control_example_failure(control_group_name, example_data, error)
- end
+ def converge_failed(exception); end
# TODO: need events for notification resolve?
# def notifications_resolved
@@ -318,109 +258,91 @@ class Chef
#
# Called before action is executed on a resource.
- def resource_action_start(resource, action, notification_type = nil, notifier = nil)
- end
+ def resource_action_start(resource, action, notification_type = nil, notifier = nil); end
# Called when a resource action has been skipped b/c of a conditional
- def resource_skipped(resource, action, conditional)
- end
+ def resource_skipped(resource, action, conditional); end
# Called after #load_current_resource has run.
- def resource_current_state_loaded(resource, action, current_resource)
- end
+ def resource_current_state_loaded(resource, action, current_resource); end
+
+ # Called after #load_after_resource has run.
+ def resource_after_state_loaded(resource, action, after_resource); end
# Called when resource current state load is skipped due to the provider
# not supporting whyrun mode.
- def resource_current_state_load_bypassed(resource, action, current_resource)
- end
+ def resource_current_state_load_bypassed(resource, action, current_resource); end
# Called when evaluating a resource that does not support whyrun in whyrun mode
- def resource_bypassed(resource, action, current_resource)
- end
+ def resource_bypassed(resource, action, current_resource); 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
+ def resource_update_applied(resource, action, update); end
# Called when a progress notification should be sent to the user to
# indicate the overall progress of a long running operation, such as
# a large file download.
- def resource_update_progress(resource, current, total, interval)
- end
+ def resource_update_progress(resource, current, total, interval); end
# Called when a resource fails, but will retry.
- def resource_failed_retriable(resource, action, retry_count, exception)
- end
+ 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
+ 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
+ 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
+ def resource_up_to_date(resource, action); end
# Called when a resource action has been completed
- def resource_completed(resource)
- end
+ def resource_completed(resource); end
# A stream has opened.
- def stream_opened(stream, options = {})
- end
+ def stream_opened(stream, options = {}); end
# A stream has closed.
- def stream_closed(stream, options = {})
- end
+ def stream_closed(stream, options = {}); end
# A chunk of data from a stream. The stream is managed by "stream," which
# can be any tag whatsoever. Data in different "streams" may not be placed
# on the same line or even sent to the same console.
- def stream_output(stream, output, options = {})
- end
+ def stream_output(stream, output, options = {}); end
# Called before handlers run
- def handlers_start(handler_count)
- end
+ def handlers_start(handler_count); end
# Called after an individual handler has run
- def handler_executed(handler)
- end
+ def handler_executed(handler); end
# Called after all handlers have executed
- def handlers_completed
- end
+ def handlers_completed; end
# Called when an assertion declared by a provider fails
- def provider_requirement_failed(action, resource, exception, message)
- end
+ def provider_requirement_failed(action, resource, exception, message); end
# Called when a provider makes an assumption after a failed assertion
# in whyrun mode, in order to allow execution to continue
- def whyrun_assumption(action, resource, message)
- end
+ def whyrun_assumption(action, resource, message); end
# Emit a message about something being deprecated.
- def deprecation(message, location = caller(2..2)[0])
- end
+ def deprecation(message, location = caller(2..2)[0]); end
- def run_list_expanded(run_list_expansion)
- 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
# there's no semantic information about the content or importance of the
# message. That means that if you're using this too often, you should add a
# callback for it.
- def msg(message)
- end
+ def msg(message); end
+ # Called when an attribute is changed by simple assignment
+ def attribute_changed(precedence, keys, value); end
end
end
end
diff --git a/lib/chef/event_dispatch/dispatcher.rb b/lib/chef/event_dispatch/dispatcher.rb
index 69419a393b..b4a2d7879b 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_relative "base"
class Chef
module EventDispatch
@@ -15,14 +15,46 @@ class Chef
@subscribers = subscribers
end
+ # Since the cookbook synchronizer will call this object from threads, we
+ # have to deal with concurrent access to this object. Since we don't want
+ # threads to handle events from other threads, we just use thread local
+ # storage.
+ #
+ def event_list
+ Thread.current[:chef_client_event_list] ||= []
+ end
+
# Add a new subscriber to the list of registered subscribers
def register(subscriber)
- @subscribers << subscriber
+ subscribers << subscriber
+ end
+
+ def unregister(subscriber)
+ subscribers.reject! { |x| x == subscriber }
+ end
+
+ def enqueue(method_name, *args)
+ event_list << [ method_name, *args ]
+ process_events_until_done unless @in_call
+ end
+
+ (Base.instance_methods - Object.instance_methods).each do |method_name|
+ class_eval <<-EOM
+ def #{method_name}(*args)
+ enqueue(#{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])
+ enqueue(:deprecation, message, location)
end
# Check to see if we are dispatching to a formatter
+ # @api private
def formatter?
- @subscribers.any? { |s| s.respond_to?(:is_formatter?) && s.is_formatter? }
+ subscribers.any? { |s| s.respond_to?(:is_formatter?) && s.is_formatter? }
end
####
@@ -30,10 +62,13 @@ class Chef
# define the forwarding in one go:
#
+ # @api private
def call_subscribers(method_name, *args)
- @subscribers.each do |s|
- # Skip new/unsupported event names.
- next if !s.respond_to?(method_name)
+ @in_call = true
+ subscribers.each do |s|
+ # Skip new/unsupported event names
+ next unless 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.
@@ -43,20 +78,19 @@ class Chef
mth.call(*args)
end
end
+ ensure
+ @in_call = false
end
- (Base.instance_methods - Object.instance_methods).each do |method_name|
- class_eval <<-EOM
- def #{method_name}(*args)
- call_subscribers(#{method_name.inspect}, *args)
- end
- EOM
- end
+ private
- # Special case deprecation, since it needs to know its caller
- def deprecation(message, location = caller(2..2)[0])
- call_subscribers(:deprecation, message, location)
+ # events are allowed to enqueue chained events, so pop them off until
+ # empty, rather than iterating over the list.
+ #
+ def process_events_until_done
+ call_subscribers(*event_list.shift) until event_list.empty?
end
+
end
end
end
diff --git a/lib/chef/event_dispatch/dsl.rb b/lib/chef/event_dispatch/dsl.rb
index 999d536fbe..275506a4ec 100644
--- a/lib/chef/event_dispatch/dsl.rb
+++ b/lib/chef/event_dispatch/dsl.rb
@@ -15,9 +15,9 @@
# 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"
+require_relative "base"
+require_relative "../exceptions"
+require_relative "../config"
class Chef
module EventDispatch
@@ -35,10 +35,10 @@ class Chef
# 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::Log.trace("Registering handler '#{name}' using events api")
Chef.run_context.events.register(handler)
else
- Chef::Log.debug("Registering handler '#{name}' using global config")
+ Chef::Log.trace("Registering handler '#{name}' using global config")
Chef::Config[:event_handlers] << handler
end
end
diff --git a/lib/chef/event_loggers/base.rb b/lib/chef/event_loggers/base.rb
index 3c11e809f6..1ef7f7de6c 100644
--- a/lib/chef/event_loggers/base.rb
+++ b/lib/chef/event_loggers/base.rb
@@ -1,7 +1,7 @@
#
# Author:: Jay Mundrawala (<jdm@chef.io>)
#
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require "chef/event_dispatch/base"
+require_relative "../event_dispatch/base"
class Chef
module EventLoggers
@@ -43,8 +43,9 @@ class Chef
def self.new(name)
event_logger_class = by_name(name.to_s)
- raise UnknownEventLogger, "No event logger found for #{name} (available: #{available_event_loggers.join(', ')})" unless event_logger_class
+ raise UnknownEventLogger, "No event logger found for #{name} (available: #{available_event_loggers.join(", ")})" unless event_logger_class
raise UnavailableEventLogger unless available_event_loggers.include? name.to_s
+
event_logger_class.new
end
diff --git a/lib/chef/event_loggers/windows_eventlog.rb b/lib/chef/event_loggers/windows_eventlog.rb
index f8c3d346c5..6b290eb8a7 100644
--- a/lib/chef/event_loggers/windows_eventlog.rb
+++ b/lib/chef/event_loggers/windows_eventlog.rb
@@ -1,7 +1,7 @@
#
# Author:: Jay Mundrawala (<jdm@chef.io>)
#
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,9 +16,10 @@
# limitations under the License.
#
-require "chef/event_loggers/base"
-require "chef/platform/query_helpers"
-require "chef/win32/eventlog"
+require_relative "base"
+require_relative "../platform/query_helpers"
+require_relative "../win32/eventlog"
+require "chef-utils" unless defined?(ChefUtils::CANARY)
class Chef
module EventLoggers
@@ -35,48 +36,48 @@ class Chef
LOG_CATEGORY_ID = 11001
# Since we must install the event logger, this is not really configurable
- SOURCE = "Chef"
+ SOURCE = ChefUtils::Dist::Infra::SHORT.freeze
def self.available?
- return Chef::Platform.windows?
+ ChefUtils.windows?
end
def initialize
@eventlog = ::Win32::EventLog.open("Application")
end
- def run_start(version)
+ def run_start(version, run_status)
@eventlog.report_event(
- :event_type => ::Win32::EventLog::INFO_TYPE,
- :source => SOURCE,
- :event_id => RUN_START_EVENT_ID,
- :data => [version]
+ event_type: ::Win32::EventLog::INFO_TYPE,
+ source: SOURCE,
+ event_id: RUN_START_EVENT_ID,
+ data: [version]
)
end
def run_started(run_status)
@run_status = run_status
@eventlog.report_event(
- :event_type => ::Win32::EventLog::INFO_TYPE,
- :source => SOURCE,
- :event_id => RUN_STARTED_EVENT_ID,
- :data => [run_status.run_id]
+ event_type: ::Win32::EventLog::INFO_TYPE,
+ source: SOURCE,
+ event_id: RUN_STARTED_EVENT_ID,
+ data: [run_status.run_id]
)
end
def run_completed(node)
@eventlog.report_event(
- :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]
+ 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]
)
end
- #Failed chef-client run %1 in %2 seconds.
- #Exception type: %3
- #Exception message: %4
- #Exception backtrace: %5
+ # Failed chef-client run %1 in %2 seconds.
+ # Exception type: %3
+ # Exception message: %4
+ # Exception backtrace: %5
def run_failed(e)
data =
if @run_status
@@ -87,10 +88,10 @@ class Chef
end
@eventlog.report_event(
- :event_type => ::Win32::EventLog::ERROR_TYPE,
- :source => SOURCE,
- :event_id => RUN_FAILED_EVENT_ID,
- :data => data + [e.class.name,
+ event_type: ::Win32::EventLog::ERROR_TYPE,
+ source: SOURCE,
+ event_id: RUN_FAILED_EVENT_ID,
+ data: data + [e.class.name,
e.message,
e.backtrace.join("\n")]
)
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb
index a4d5ff60e2..a0afd25208 100644
--- a/lib/chef/exceptions.rb
+++ b/lib/chef/exceptions.rb
@@ -2,7 +2,7 @@
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Seth Falcon (<seth@chef.io>)
# Author:: Kyle Goodwin (<kgoodwin@primerevenue.com>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,6 +18,8 @@
# limitations under the License.
require "chef-config/exceptions"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+require_relative "constants"
class Chef
# == Chef::Exceptions
@@ -45,8 +47,9 @@ class Chef
class SigInt < RuntimeError; end
class SigTerm < RuntimeError; end
class Cron < RuntimeError; end
- class Env < RuntimeError; end
+ class WindowsEnv < RuntimeError; end
class Exec < RuntimeError; end
+ class Execute < RuntimeError; end
class ErlCall < RuntimeError; end
class FileNotFound < RuntimeError; end
class Package < RuntimeError; end
@@ -58,14 +61,6 @@ class Chef
class UnsupportedAction < RuntimeError; end
class MissingLibrary < RuntimeError; end
- class DeprecatedExitCode < RuntimeError
- def initalize
- super "Exiting with a non RFC 062 Exit Code."
- require "chef/application/exit_code"
- Chef::Application::ExitCode.notify_deprecated_exit_code
- end
- end
-
class CannotDetermineNodeName < RuntimeError
def initialize
super "Unable to determine node name: configure node_name or configure the system's hostname and fqdn"
@@ -76,9 +71,10 @@ class Chef
class Group < RuntimeError; end
class Link < RuntimeError; end
class Mount < RuntimeError; end
- class Reboot < Exception; end
- class RebootPending < Exception; end
+ class Reboot < Exception; end # rubocop:disable Lint/InheritException
+ class RebootPending < Exception; end # rubocop:disable Lint/InheritException
class RebootFailed < Mixlib::ShellOut::ShellCommandFailed; end
+ class ClientUpgraded < Exception; end # rubocop:disable Lint/InheritException
class PrivateKeyMissing < RuntimeError; end
class CannotWritePrivateKey < RuntimeError; end
class RoleNotFound < RuntimeError; end
@@ -88,11 +84,13 @@ class Chef
class InvalidPrivateKey < 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
@@ -105,6 +103,7 @@ class Chef
# Cookbook loader used to raise an argument error when cookbook not found.
# for back compat, need to raise an error that inherits from ArgumentError
class CookbookNotFoundInRepo < ArgumentError; end
+ class CookbookMergingError < RuntimeError; end
class RecipeNotFound < ArgumentError; end
# AttributeNotFound really means the attribute file could not be found
class AttributeNotFound < RuntimeError; end
@@ -136,7 +135,7 @@ class Chef
# Can't find a Resource of this type that is valid on this platform.
class NoSuchResourceType < NameError
def initialize(short_name, node)
- super "Cannot find a resource for #{short_name} on #{node[:platform]} version #{node[:platform_version]}"
+ super "Cannot find a resource for #{short_name} on #{node[:platform]} version #{node[:platform_version]} with target_mode? #{Chef::Config.target_mode?}"
end
end
@@ -158,7 +157,7 @@ class Chef
# Thrown when Win32 API layer binds to non-existent Win32 function. Occurs
# when older versions of Windows don't support newer Win32 API functions.
- class Win32APIFunctionNotImplemented < NotImplementedError; end
+ class Win32APIFunctionNotImplemented < NotImplementedError; end # rubocop:disable Lint/InheritException
# Attempting to run windows code on a not-windows node
class Win32NotWindows < RuntimeError; end
class WindowsNotAdmin < RuntimeError; end
@@ -195,12 +194,14 @@ class Chef
class InvalidVersionConstraint < ArgumentError; end
# Version constraints are not allowed in chef-solo
- class IllegalVersionConstraint < NotImplementedError; end
+ class IllegalVersionConstraint < NotImplementedError; end # rubocop:disable Lint/InheritException
class MetadataNotValid < StandardError; end
+
class MetadataNotFound < StandardError
attr_reader :install_path
attr_reader :cookbook_name
+
def initialize(install_path, cookbook_name)
@install_path = install_path
@cookbook_name = cookbook_name
@@ -245,6 +246,10 @@ class Chef
class Win32RegBadValueSize < ArgumentError; end
class Win32RegTypesMismatch < ArgumentError; end
+ # incorrect input for registry_key create action throws following error
+ class RegKeyValuesTypeMissing < ArgumentError; end
+ class RegKeyValuesDataMissing < ArgumentError; end
+
class InvalidEnvironmentPath < ArgumentError; end
class EnvironmentNotFound < RuntimeError; end
@@ -257,21 +262,19 @@ class Chef
class ChildConvergeError < RuntimeError; end
- class DeprecatedFeatureError < RuntimeError;
- def initalize(message)
+ class DeprecatedFeatureError < RuntimeError
+ def initialize(message)
super("#{message} (raising error due to treat_deprecation_warnings_as_errors being set)")
end
end
class MissingRole < RuntimeError
- NULL = Object.new
-
attr_reader :expansion
- def initialize(message_or_expansion = NULL)
+ def initialize(message_or_expansion = NOT_PASSED)
@expansion = nil
case message_or_expansion
- when NULL
+ when NOT_PASSED
super()
when String
super
@@ -283,6 +286,7 @@ class Chef
end
end
+
# Exception class for collecting multiple failures. Used when running
# delayed notifications so that chef can process each delayed
# notification even if chef client or other notifications fail.
@@ -301,7 +305,7 @@ class Chef
def client_run_failure(exception)
set_backtrace(exception.backtrace)
- @all_failures << [ "chef run", exception ]
+ @all_failures << [ "#{ChefUtils::Dist::Infra::PRODUCT} run", exception ]
end
def notification_failure(exception)
@@ -310,7 +314,7 @@ class Chef
def raise!
unless empty?
- raise self.for_raise
+ raise for_raise
end
end
@@ -400,9 +404,9 @@ class Chef
# length declared in the http response.
class ContentLengthMismatch < RuntimeError
def initialize(response_length, 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.
+ 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 #{ChefUtils::Dist::Infra::CLIENT}.
EOF
end
end
@@ -414,7 +418,7 @@ This error is most often caused by network issues (proxies, etc) outside of chef
end
# Raised when Chef::Config[:run_lock_timeout] is set and some other client run fails
- # to release the run lock becure Chef::Config[:run_lock_timeout] seconds pass.
+ # to release the run lock before Chef::Config[:run_lock_timeout] seconds pass.
class RunLockTimeout < RuntimeError
def initialize(duration, blocking_pid)
super "Unable to acquire lock. Waited #{duration} seconds for #{blocking_pid} to release."
@@ -423,7 +427,7 @@ This error is most often caused by network issues (proxies, etc) outside of chef
class ChecksumMismatch < RuntimeError
def initialize(res_cksum, cont_cksum)
- super "Checksum on resource (#{res_cksum}) does not match checksum on content (#{cont_cksum})"
+ super "Checksum on resource (#{res_cksum}...) does not match checksum on content (#{cont_cksum}...)"
end
end
@@ -444,32 +448,14 @@ This error is most often caused by network issues (proxies, etc) outside of chef
end
end
- class AuditError < RuntimeError; end
-
- class AuditControlGroupDuplicate < AuditError
- def initialize(name)
- super "Control group with name '#{name}' has already been defined"
- end
- end
- class AuditNameMissing < AuditError; end
- class NoAuditsProvided < AuditError
- def initialize
- super "You must provide a block with controls"
- end
- end
- class AuditsFailed < AuditError
- def initialize(num_failed, num_total)
- super "Audit phase found failures - #{num_failed}/#{num_total} controls failed"
- end
- end
-
- # If a converge or audit fails, we want to wrap the output from those errors into 1 error so we can
+ # If a converge fails, we want to wrap the output from those errors into 1 error so we can
# see both issues in the output. It is possible that nil will be provided. You must call `fill_backtrace`
# to correctly populate the backtrace with the wrapped backtraces.
class RunFailedWrappingError < RuntimeError
attr_reader :wrapped_errors
+
def initialize(*errors)
- errors = errors.select { |e| !e.nil? }
+ errors = errors.compact
output = "Found #{errors.size} errors, they are stored in the backtrace"
@wrapped_errors = errors
super output
@@ -495,7 +481,7 @@ This error is most often caused by network issues (proxies, etc) outside of chef
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}"
+ super "Cookbook '#{cookbook_name}' version '#{cookbook_version}' depends on #{ChefUtils::Dist::Infra::PRODUCT} version #{constraint_str}, but the running #{ChefUtils::Dist::Infra::PRODUCT} version is #{chef_version}"
end
end
@@ -508,16 +494,38 @@ This error is most often caused by network issues (proxies, etc) outside of chef
class MultipleDscResourcesFound < RuntimeError
attr_reader :resources_found
+
def initialize(resources_found)
@resources_found = resources_found
matches_info = @resources_found.each do |r|
if r["Module"].nil?
- "Resource #{r['Name']} was found in #{r['Module']['Name']}"
+ "Resource #{r["Name"]} was found in #{r["Module"]["Name"]}"
else
- "Resource #{r['Name']} is a binary resource"
+ "Resource #{r["Name"]} is a binary resource"
end
end
- super "Found multiple matching resources. #{matches_info.join("\n")}"
+ super "Found multiple resources matching #{matches_info[0]["Module"]["Name"]}:\n#{(matches_info.map { |f| f["Module"]["Version"] }).uniq.join("\n")}"
+ end
+ end
+
+ # exception specific to invalid usage of 'dsc_resource' resource
+ class DSCModuleNameMissing < ArgumentError; end
+
+ class GemRequirementConflict < RuntimeError
+ def initialize(gem_name, option, value1, value2)
+ super "Conflicting requirements for gem '#{gem_name}': Both #{value1.inspect} and #{value2.inspect} given for option #{option.inspect}"
+ end
+ end
+
+ class UnifiedModeImmediateSubscriptionEarlierResource < RuntimeError
+ def initialize(notification)
+ super "immediate subscription from #{notification.resource} resource cannot be setup to #{notification.notifying_resource} resource, which has already fired while in unified mode"
+ end
+ end
+
+ class UnifiedModeBeforeSubscriptionEarlierResource < RuntimeError
+ def initialize(notification)
+ super "before subscription from #{notification.resource} resource cannot be setup to #{notification.notifying_resource} resource, which has already fired while in unified mode"
end
end
end
diff --git a/lib/chef/file_access_control.rb b/lib/chef/file_access_control.rb
index 50a1ea29bb..c192a24d4d 100644
--- a/lib/chef/file_access_control.rb
+++ b/lib/chef/file_access_control.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/log"
+require_relative "log"
class Chef
@@ -26,11 +26,11 @@ class Chef
# the values specified by a value object, usually a Chef::Resource.
class FileAccessControl
- if RUBY_PLATFORM =~ /mswin|mingw|windows/
- require "chef/file_access_control/windows"
+ if RUBY_PLATFORM.match?(/mswin|mingw|windows/)
+ require_relative "file_access_control/windows"
include FileAccessControl::Windows
else
- require "chef/file_access_control/unix"
+ require_relative "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 1746db44d3..9ea24b3b39 100644
--- a/lib/chef/file_access_control/unix.rb
+++ b/lib/chef/file_access_control/unix.rb
@@ -2,7 +2,7 @@
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Daniel DeLeo (<dan@chef.io>)
# Author:: Seth Chisamore (<schisamo@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,7 +18,7 @@
# limitations under the License.
#
-require "chef/log"
+require_relative "../log"
class Chef
class FileAccessControl
@@ -79,20 +79,20 @@ 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")
- return false
+ Chef::Log.trace("Found target_uid == nil, so no owner was specified on resource, not managing owner")
+ false
elsif current_uid.nil?
# the user has specified a permission, and we are creating a file, so always enforce permissions
- Chef::Log.debug("Found current_uid == nil, so we are creating a new file, updating owner")
- return true
+ Chef::Log.trace("Found current_uid == nil, so we are creating a new file, updating owner")
+ true
elsif target_uid != current_uid
# the user has specified a permission, and it does not match the file, so fix the permission
- Chef::Log.debug("Found target_uid != current_uid, updating owner")
- return true
+ Chef::Log.trace("Found target_uid != current_uid, updating owner")
+ true
else
- Chef::Log.debug("Found target_uid == current_uid, not updating owner")
+ Chef::Log.trace("Found target_uid == current_uid, not updating owner")
# the user has specified a permission, but it matches the file, so behave idempotently
- return false
+ false
end
end
@@ -117,13 +117,14 @@ class Chef
end
def gid_from_resource(resource)
- return nil if resource == nil || resource.group.nil?
- if resource.group.kind_of?(String)
+ return nil if resource.nil? || resource.group.nil?
+
+ if resource.group.is_a?(String)
diminished_radix_complement( Etc.getgrnam(resource.group).gid )
- elsif resource.group.kind_of?(Integer)
+ elsif resource.group.is_a?(Integer)
resource.group
else
- Chef::Log.error("The `group` parameter of the #@resource resource is set to an invalid value (#{resource.owner.inspect})")
+ Chef::Log.error("The `group` parameter of the #{@resource} resource is set to an invalid value (#{resource.owner.inspect})")
raise ArgumentError, "cannot resolve #{resource.group.inspect} to gid, group must be a string or integer"
end
rescue ArgumentError
@@ -132,26 +133,26 @@ class Chef
a.failure_message(Chef::Exceptions::GroupIDNotFound, "cannot determine group id for '#{resource.group}', does the group exist on this system?")
a.whyrun("Assuming group #{resource.group} would have been created")
end
- return nil
+ nil
end
def should_update_group?
if target_gid.nil?
# the user has not specified a permission on the new resource, so we never manage it with FAC
- Chef::Log.debug("Found target_gid == nil, so no group was specified on resource, not managing group")
- return false
+ Chef::Log.trace("Found target_gid == nil, so no group was specified on resource, not managing group")
+ false
elsif current_gid.nil?
# the user has specified a permission, and we are creating a file, so always enforce permissions
- Chef::Log.debug("Found current_gid == nil, so we are creating a new file, updating group")
- return true
+ Chef::Log.trace("Found current_gid == nil, so we are creating a new file, updating group")
+ true
elsif target_gid != current_gid
# the user has specified a permission, and it does not match the file, so fix the permission
- Chef::Log.debug("Found target_gid != current_gid, updating group")
- return true
+ Chef::Log.trace("Found target_gid != current_gid, updating group")
+ true
else
- Chef::Log.debug("Found target_gid == current_gid, not updating group")
+ Chef::Log.trace("Found target_gid == current_gid, not updating group")
# the user has specified a permission, but it matches the file, so behave idempotently
- return false
+ false
end
end
@@ -168,7 +169,8 @@ class Chef
end
def mode_from_resource(res)
- return nil if res == nil || res.mode.nil?
+ return nil if res.nil? || res.mode.nil?
+
(res.mode.respond_to?(:oct) ? res.mode.oct : res.mode.to_i) & 007777
end
@@ -187,22 +189,22 @@ 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")
- return false
+ Chef::Log.trace("Found target_mode == nil, so no mode was specified on resource, not managing mode")
+ false
elsif current_mode.nil?
# the user has specified a permission, and we are creating a file, so always enforce permissions
- Chef::Log.debug("Found current_mode == nil, so we are creating a new file, updating mode")
- return true
+ Chef::Log.trace("Found current_mode == nil, so we are creating a new file, updating mode")
+ true
elsif target_mode != current_mode
# the user has specified a permission, and it does not match the file, so fix the permission
- Chef::Log.debug("Found target_mode != current_mode, updating mode")
- return true
+ Chef::Log.trace("Found target_mode != current_mode, updating mode")
+ true
elsif suid_bit_set? && (should_update_group? || should_update_owner?)
- return true
+ true
else
- Chef::Log.debug("Found target_mode == current_mode, not updating mode")
+ Chef::Log.trace("Found target_mode == current_mode, not updating mode")
# the user has specified a permission, but it matches the file, so behave idempotently
- return false
+ false
end
end
@@ -264,13 +266,14 @@ class Chef
end
def uid_from_resource(resource)
- return nil if resource == nil || resource.owner.nil?
- if resource.owner.kind_of?(String)
+ return nil if resource.nil? || resource.owner.nil?
+
+ if resource.owner.is_a?(String)
diminished_radix_complement( Etc.getpwnam(resource.owner).uid )
- elsif resource.owner.kind_of?(Integer)
+ elsif resource.owner.is_a?(Integer)
resource.owner
else
- Chef::Log.error("The `owner` parameter of the #@resource resource is set to an invalid value (#{resource.owner.inspect})")
+ Chef::Log.error("The `owner` parameter of the #{@resource} resource is set to an invalid value (#{resource.owner.inspect})")
raise ArgumentError, "cannot resolve #{resource.owner.inspect} to uid, owner must be a string or integer"
end
rescue ArgumentError
@@ -279,11 +282,11 @@ class Chef
a.failure_message(Chef::Exceptions::UserIDNotFound, "cannot determine user id for '#{resource.owner}', does the user exist on this system?")
a.whyrun("Assuming user #{resource.owner} would have been created")
end
- return nil
+ nil
end
def suid_bit_set?
- return target_mode & 04000 > 0
+ target_mode & 04000 > 0
end
end
end
diff --git a/lib/chef/file_access_control/windows.rb b/lib/chef/file_access_control/windows.rb
index 6f1ac5f581..cc1b96a84d 100644
--- a/lib/chef/file_access_control/windows.rb
+++ b/lib/chef/file_access_control/windows.rb
@@ -1,7 +1,7 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
# Author:: Seth Chisamore (<schisamo@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/win32/security"
-require "chef/win32/file"
+require_relative "../win32/security"
+require_relative "../win32/file"
class Chef
class FileAccessControl
@@ -34,7 +34,8 @@ class Chef
# We want to mix these in as class methods
def writable?(path)
::File.exists?(path) && Chef::ReservedNames::Win32::File.file_access_check(
- path, Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_WRITE)
+ path, Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_WRITE
+ )
end
end
@@ -90,17 +91,19 @@ class Chef
target_acl.each do |target_ace|
if target_ace.flags & INHERIT_ONLY_ACE == 0
self_ace = target_ace.dup
- self_ace.flags = 0
+ # We need flag value which is already being set in case of WRITE permissions as 3, so we will not be overwriting it with the hard coded value.
+ self_ace.flags = 0 unless target_ace.mask == Chef::ReservedNames::Win32::API::Security::WRITE
self_ace.mask = securable_object.predict_rights_mask(target_ace.mask)
new_target_acl << self_ace
end
- if target_ace.flags & (CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE) != 0
+ # As there is no inheritance needed in case of WRITE permissions.
+ if target_ace.mask != Chef::ReservedNames::Win32::API::Security::WRITE && target_ace.flags & (CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE) != 0
children_ace = target_ace.dup
children_ace.flags |= INHERIT_ONLY_ACE
new_target_acl << children_ace
end
end
- return actual_acl == new_target_acl
+ actual_acl == new_target_acl
end
def existing_descriptor
@@ -108,9 +111,13 @@ class Chef
end
def get_sid(value)
- if value.kind_of?(String)
- SID.from_account(value)
- elsif value.kind_of?(SID)
+ if value.is_a?(String)
+ begin
+ Security.convert_string_sid_to_sid(value)
+ rescue Chef::Exceptions::Win32APIError
+ SID.from_account(value)
+ end
+ elsif value.is_a?(SID)
value
else
raise "Must specify username, group or SID: #{value}"
@@ -119,16 +126,18 @@ class Chef
def securable_object
@securable_object ||= begin
- if file.kind_of?(String)
+ if file.is_a?(String)
so = Chef::ReservedNames::Win32::Security::SecurableObject.new(file.dup)
end
- raise ArgumentError, "'file' must be a valid path or object of type 'Chef::ReservedNames::Win32::Security::SecurableObject'" unless so.kind_of? Chef::ReservedNames::Win32::Security::SecurableObject
+ raise ArgumentError, "'file' must be a valid path or object of type 'Chef::ReservedNames::Win32::Security::SecurableObject'" unless so.is_a? Chef::ReservedNames::Win32::Security::SecurableObject
+
so
end
end
def should_update_dacl?
return true unless ::File.exists?(file) || ::File.symlink?(file)
+
dacl = target_dacl
existing_dacl = existing_descriptor.dacl
inherits = target_inherits
@@ -162,6 +171,7 @@ class Chef
def should_update_group?
return true unless ::File.exists?(file) || ::File.symlink?(file)
+
(group = target_group) && (group != existing_descriptor.group)
end
@@ -181,6 +191,7 @@ class Chef
def should_update_owner?
return true unless ::File.exists?(file) || ::File.symlink?(file)
+
(owner = target_owner) && (owner != existing_descriptor.owner)
end
@@ -204,6 +215,7 @@ class Chef
mask |= (GENERIC_WRITE | DELETE) if mode & 2 != 0
mask |= GENERIC_EXECUTE if mode & 1 != 0
return [] if mask == 0
+
[ ACE.access_allowed(sid, mask) ]
end
@@ -220,7 +232,7 @@ class Chef
when :read_execute
mask |= GENERIC_READ | GENERIC_EXECUTE
when :write
- mask |= GENERIC_WRITE
+ mask |= WRITE
else
# Otherwise, assume it's an integer specifying the actual flags
mask |= permission
@@ -234,7 +246,7 @@ class Chef
flags = 0
#
- # Configure child inheritence only if the resource is some
+ # Configure child inheritance only if the resource is some
# type of a directory.
#
if resource.is_a? Chef::Resource::Directory
@@ -243,10 +255,7 @@ class Chef
flags |= CONTAINER_INHERIT_ACE
when :objects_only
flags |= OBJECT_INHERIT_ACE
- when true
- flags |= CONTAINER_INHERIT_ACE
- flags |= OBJECT_INHERIT_ACE
- when nil
+ when true, nil
flags |= CONTAINER_INHERIT_ACE
flags |= OBJECT_INHERIT_ACE
end
@@ -264,9 +273,10 @@ class Chef
def target_dacl
return nil if resource.rights.nil? && resource.deny_rights.nil? && resource.mode.nil?
+
acls = nil
- if !resource.deny_rights.nil?
+ unless resource.deny_rights.nil?
acls = [] if acls.nil?
resource.deny_rights.each do |rights|
@@ -279,7 +289,7 @@ class Chef
end
end
- if !resource.rights.nil?
+ unless resource.rights.nil?
acls = [] if acls.nil?
resource.rights.each do |rights|
@@ -292,7 +302,7 @@ class Chef
end
end
- if !resource.mode.nil?
+ unless resource.mode.nil?
acls = [] if acls.nil?
mode = (resource.mode.respond_to?(:oct) ? resource.mode.oct : resource.mode.to_i) & 0777
@@ -319,6 +329,7 @@ class Chef
def target_group
return nil if resource.group.nil?
+
get_sid(resource.group)
end
@@ -328,6 +339,7 @@ class Chef
def target_owner
return nil if resource.owner.nil?
+
get_sid(resource.owner)
end
end
diff --git a/lib/chef/file_cache.rb b/lib/chef/file_cache.rb
index 8e9bb1e3e4..22060869da 100644
--- a/lib/chef/file_cache.rb
+++ b/lib/chef/file_cache.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -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_relative "mixin/params_validate"
+require_relative "mixin/create_path"
+require_relative "exceptions"
+require_relative "json_compat"
+require "fileutils" unless defined?(FileUtils)
+require_relative "util/path_helper"
class Chef
class FileCache
@@ -42,12 +42,12 @@ class Chef
def store(path, contents, perm = 0640)
validate(
{
- :path => path,
- :contents => contents,
+ path: path,
+ contents: contents,
},
{
- :path => { :kind_of => String },
- :contents => { :kind_of => String },
+ path: { kind_of: String },
+ contents: { kind_of: String },
}
)
@@ -68,12 +68,12 @@ class Chef
def move_to(file, path)
validate(
{
- :file => file,
- :path => path,
+ file: file,
+ path: path,
},
{
- :file => { :kind_of => String },
- :path => { :kind_of => String },
+ file: { kind_of: String },
+ path: { kind_of: String },
}
)
@@ -105,14 +105,15 @@ class Chef
def load(path, read = true)
validate(
{
- :path => path,
+ path: path,
},
{
- :path => { :kind_of => String },
+ 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)
+
if read
File.read(cache_path)
else
@@ -131,10 +132,10 @@ class Chef
def delete(path)
validate(
{
- :path => path,
+ path: path,
},
{
- :path => { :kind_of => String },
+ path: { kind_of: String },
}
)
cache_path = create_cache_path(path, false)
@@ -157,7 +158,7 @@ class Chef
# === Returns
# [String] - An array of file cache keys matching the glob
def find(glob_pattern)
- keys = Array.new
+ keys = []
Dir[File.join(Chef::Util::PathHelper.escape_glob_dir(file_cache_path), glob_pattern)].each do |f|
if File.file?(f)
keys << f[/^#{Regexp.escape(Dir[Chef::Util::PathHelper.escape_glob_dir(file_cache_path)].first) + File::Separator}(.+)/, 1]
@@ -175,13 +176,13 @@ class Chef
# === Returns
# True:: If the file exists
# False:: If it does not
- def has_key?(path)
+ def key?(path)
validate(
{
- :path => path,
+ path: path,
},
{
- :path => { :kind_of => String },
+ path: { kind_of: String },
}
)
full_path = create_cache_path(path, false)
@@ -192,6 +193,8 @@ class Chef
end
end
+ alias_method :has_key?, :key?
+
# Create a full path to a given file in the cache. By default,
# also creates the path if it does not exist.
#
diff --git a/lib/chef/file_content_management/content_base.rb b/lib/chef/file_content_management/content_base.rb
index 6080e37180..8098ea14a2 100644
--- a/lib/chef/file_content_management/content_base.rb
+++ b/lib/chef/file_content_management/content_base.rb
@@ -1,6 +1,6 @@
#
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,12 +23,14 @@ class Chef
attr_reader :run_context
attr_reader :new_resource
attr_reader :current_resource
+ attr_reader :logger
- def initialize(new_resource, current_resource, run_context)
+ def initialize(new_resource, current_resource, run_context, logger = Chef::Log.with_child)
@new_resource = new_resource
@current_resource = current_resource
@run_context = run_context
@tempfile_loaded = false
+ @logger = logger
end
def tempfile
diff --git a/lib/chef/file_content_management/deploy.rb b/lib/chef/file_content_management/deploy.rb
index 1e3ae55c21..7098817183 100644
--- a/lib/chef/file_content_management/deploy.rb
+++ b/lib/chef/file_content_management/deploy.rb
@@ -1,6 +1,6 @@
#
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,10 +16,10 @@
# limitations under the License.
#
-require "chef/file_content_management/deploy/cp"
-require "chef/file_content_management/deploy/mv_unix"
-if Chef::Platform.windows?
- require "chef/file_content_management/deploy/mv_windows"
+require_relative "deploy/cp"
+require_relative "deploy/mv_unix"
+if ChefUtils.windows?
+ require_relative "deploy/mv_windows"
end
class Chef
@@ -27,9 +27,9 @@ class Chef
class Deploy
def self.strategy(atomic_update)
if atomic_update
- Chef::Platform.windows? ? MvWindows.new() : MvUnix.new()
+ ChefUtils.windows? ? MvWindows.new : MvUnix.new
else
- Cp.new()
+ Cp.new
end
end
end
diff --git a/lib/chef/file_content_management/deploy/cp.rb b/lib/chef/file_content_management/deploy/cp.rb
index 14dde85d13..010271d1db 100644
--- a/lib/chef/file_content_management/deploy/cp.rb
+++ b/lib/chef/file_content_management/deploy/cp.rb
@@ -1,6 +1,6 @@
#
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -34,12 +34,12 @@ class Chef
#
class Cp
def create(file)
- Chef::Log.debug("Touching #{file} to create it")
+ Chef::Log.trace("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.trace("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 3805b3bef3..19345a2b26 100644
--- a/lib/chef/file_content_management/deploy/mv_unix.rb
+++ b/lib/chef/file_content_management/deploy/mv_unix.rb
@@ -1,6 +1,6 @@
#
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,22 +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.trace("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.trace("Reading modes from #{dst} file")
stat = ::File.stat(dst)
mode = stat.mode & 07777
uid = stat.uid
gid = stat.gid
- Chef::Log.debug("Applying mode = #{mode.to_s(8)}, uid = #{uid}, gid = #{gid} to #{src}")
-
- # i own the inode, so should be able to at least chmod it
- ::File.chmod(mode, src)
+ Chef::Log.trace("Applying mode = #{mode.to_s(8)}, uid = #{uid}, gid = #{gid} to #{src}")
# we may be running as non-root in which case because we are doing an mv we cannot preserve
# the file modes. after the mv we have a different inode and if we don't have rights to
@@ -54,7 +51,7 @@ class Chef
# in the case where i'm running chef-solo on my homedir as myself and some root-shell
# work has caused dotfiles of mine to change to root-owned, i'm fine with this not being
# exceptional, and i think most use cases will consider this to not be exceptional, and
- # the right thing is to fix the ownership of the file to the user running the commmand
+ # the right thing is to fix the ownership of the file to the user running the command
# (which requires write perms to the directory, or mv will throw an exception)
begin
::File.chown(uid, nil, src)
@@ -67,7 +64,11 @@ 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}")
+ # i own the inode, so should be able to at least chmod it
+ # NOTE: this must come last due to POSIX stripping sticky mode bits on chown/chgrp
+ ::File.chmod(mode, src)
+
+ Chef::Log.trace("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 0e6e6cd76f..32f6fcd105 100644
--- a/lib/chef/file_content_management/deploy/mv_windows.rb
+++ b/lib/chef/file_content_management/deploy/mv_windows.rb
@@ -1,6 +1,6 @@
#
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,9 +21,9 @@
# ACL information on the dst file.
#
-require "chef/platform/query_helpers"
-if Chef::Platform.windows?
- require "chef/win32/security"
+require_relative "../../platform/query_helpers"
+if ChefUtils.windows?
+ require_relative "../../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.trace("Touching #{file} to create it")
FileUtils.touch(file)
end
diff --git a/lib/chef/file_content_management/tempfile.rb b/lib/chef/file_content_management/tempfile.rb
index cf59a87996..27efe34191 100644
--- a/lib/chef/file_content_management/tempfile.rb
+++ b/lib/chef/file_content_management/tempfile.rb
@@ -1,6 +1,6 @@
#
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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 "tempfile"
+require "tempfile" unless defined?(Tempfile)
class Chef
class FileContentManagement
@@ -39,15 +39,15 @@ class Chef
errors = [ ]
tempfile_dirnames.each do |tempfile_dirname|
- begin
- # preserving the file extension of the target filename should be considered a public API
- tf = ::Tempfile.open([tempfile_basename, tempfile_extension], tempfile_dirname)
- break
- rescue SystemCallError => e
- message = "Creating temp file under '#{tempfile_dirname}' failed with: '#{e.message}'"
- Chef::Log.debug(message)
- errors << message
- end
+
+ # preserving the file extension of the target filename should be considered a public API
+ tf = ::Tempfile.open([tempfile_basename, tempfile_extension], tempfile_dirname)
+ break
+ rescue SystemCallError => e
+ message = "Creating temp file under '#{tempfile_dirname}' failed with: '#{e.message}'"
+ Chef::Log.trace(message)
+ errors << message
+
end
raise Chef::Exceptions::FileContentStagingError, errors if tf.nil?
@@ -67,8 +67,8 @@ class Chef
basename = ::File.basename(@new_resource.path, tempfile_extension)
# the leading "[.]chef-" here should be considered a public API and should not be changed
basename.insert 0, "chef-"
- basename.insert 0, "." unless Chef::Platform.windows? # dotfile if we're not on windows
- basename
+ basename.insert 0, "." unless ChefUtils.windows? # dotfile if we're not on windows
+ basename.scrub
end
# this is similar to File.extname() but greedy about the extension (from the first dot, not the last dot)
@@ -76,7 +76,7 @@ class Chef
# complexity here is due to supporting mangling non-UTF8 strings (e.g. latin-1 filenames with characters that are illegal in UTF-8)
b = File.basename(@new_resource.path)
i = b.index(".")
- i.nil? ? "" : b[i..-1]
+ i.nil? ? "" : b[i..].scrub
end
# Returns the possible directories for the tempfile to be created in.
diff --git a/lib/chef/formatters/base.rb b/lib/chef/formatters/base.rb
index 536bf72e02..2471cfe844 100644
--- a/lib/chef/formatters/base.rb
+++ b/lib/chef/formatters/base.rb
@@ -1,7 +1,7 @@
#
# Author:: Tyler Cloke (<tyler@chef.io>)
#
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,11 +17,11 @@
# limitations under the License.
#
-require "chef/event_dispatch/base"
-require "chef/formatters/error_inspectors"
-require "chef/formatters/error_description"
-require "chef/formatters/error_mapper"
-require "chef/formatters/indentable_output_stream"
+require_relative "../event_dispatch/base"
+require_relative "error_inspectors"
+require_relative "error_description"
+require_relative "error_mapper"
+require_relative "indentable_output_stream"
class Chef
@@ -52,7 +52,7 @@ class Chef
# TODO: is it too clever to be defining new() on a module like this?
def self.new(name, out, err)
formatter_class = by_name(name.to_s)
- raise UnknownFormatter, "No output formatter found for #{name} (available: #{available_formatters.join(', ')})" unless formatter_class
+ raise UnknownFormatter, "No output formatter found for #{name} (available: #{available_formatters.join(", ")})" unless formatter_class
formatter_class.new(out, err)
end
@@ -96,7 +96,7 @@ class Chef
if @output.indent < 0
# This is left commented out for now. We need to uncomment it and fix at least one bug in
# the formatter, and then leave this line uncommented in the future.
- #Chef::Log.warn "Internal Formatter Error -- Attempt to indent by negative number of spaces"
+ # Chef::Log.warn "Internal Formatter Error -- Attempt to indent by negative number of spaces"
@output.indent = 0
end
@output.indent
@@ -110,7 +110,7 @@ class Chef
end
def registration_failed(node_name, exception, config)
- #A Formatters::ErrorDescription object
+ # A Formatters::ErrorDescription object
description = ErrorMapper.registration_failed(node_name, exception, config)
display_error(description)
end
@@ -137,17 +137,16 @@ class Chef
def resource_failed(resource, action, exception)
description = ErrorMapper.resource_failed(resource, action, exception)
- display_error(description)
+ display_error(description) unless resource.ignore_failure && resource.ignore_failure.to_s == "quiet"
end
# Generic callback for any attribute/library/lwrp/recipe file in a
# cookbook getting loaded. The per-filetype callbacks for file load are
- # overriden so that they call this instead. This means that a subclass of
+ # overridden so that they call this instead. This means that a subclass of
# Formatters::Base can implement #file_loaded to do the same thing for
# every kind of file that Chef loads from a recipe instead of
# implementing all the per-filetype callbacks.
- def file_loaded(path)
- end
+ def file_loaded(path); end
# Generic callback for any attribute/library/lwrp/recipe file throwing an
# exception when loaded. Default behavior is to use CompileErrorInspector
@@ -212,8 +211,17 @@ class Chef
file_load_failed(path, exception)
end
- def deprecation(message, location = caller(2..2)[0])
- Chef::Log.deprecation("#{message} at #{location}")
+ # Log a deprecation warning object.
+ #
+ # @param deprecation [Chef::Deprecated::Base] Deprecation object to log.
+ # In previous versions, this could be a string. Don't do that anymore.
+ # @param location [Object] Unused, present only for compatibility.
+ def deprecation(deprecation, _location = nil)
+ Chef::Log.deprecation(deprecation.to_s) unless deprecation.silenced?
+ end
+
+ def is_structured_deprecation?(deprecation)
+ deprecation.is_a?(Chef::Deprecated::Base)
end
def is_formatter?
diff --git a/lib/chef/formatters/doc.rb b/lib/chef/formatters/doc.rb
index 7dbbf1d948..513ac45471 100644
--- a/lib/chef/formatters/doc.rb
+++ b/lib/chef/formatters/doc.rb
@@ -1,5 +1,6 @@
-require "chef/formatters/base"
-require "chef/config"
+require_relative "base"
+require_relative "../config"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
module Formatters
@@ -8,8 +9,7 @@ class Chef
# show context.
class Doc < Formatters::Base
- attr_reader :start_time, :end_time, :successful_audits, :failed_audits
- private :successful_audits, :failed_audits
+ attr_reader :start_time, :end_time
cli_name(:doc)
@@ -18,8 +18,6 @@ class Chef
@updated_resources = 0
@up_to_date_resources = 0
- @successful_audits = 0
- @failed_audits = 0
@start_time = Time.now
@end_time = @start_time
@skipped_resources = 0
@@ -42,8 +40,10 @@ class Chef
message
end
- def run_start(version)
- puts_line "Starting Chef Client, version #{version}"
+ def run_start(version, run_status)
+ puts_line "Starting #{ChefUtils::Dist::Infra::PRODUCT}, version #{version}"
+ puts_line "Patents: #{ChefUtils::Dist::Org::PATENTS}"
+ puts_line "Targeting node: #{Chef::Config.target_mode.host}" if Chef::Config.target_mode?
puts_line "OpenSSL FIPS 140 mode enabled" if Chef::Config[:fips]
end
@@ -51,19 +51,16 @@ class Chef
@up_to_date_resources + @updated_resources + @skipped_resources
end
- def total_audits
- successful_audits + failed_audits
- end
-
def run_completed(node)
@end_time = Time.now
# Print out deprecations.
- if !deprecations.empty?
+ unless deprecations.empty?
puts_line ""
puts_line "Deprecated features used!"
- deprecations.each do |message, locations|
+ deprecations.each do |message, details|
+ locations = details[:locations]
if locations.size == 1
- puts_line " #{message} at #{locations.size} location:"
+ puts_line " #{message} at 1 location:"
else
puts_line " #{message} at #{locations.size} locations:"
end
@@ -74,49 +71,42 @@ class Chef
prefix = " "
end
end
+ unless details[:url].nil?
+ puts_line " See #{details[:url]} for further details."
+ end
end
puts_line ""
end
if Chef::Config[:why_run]
- puts_line "Chef Client finished, #{@updated_resources}/#{total_resources} resources would have been updated"
+ puts_line "#{ChefUtils::Dist::Infra::PRODUCT} finished, #{@updated_resources}/#{total_resources} resources would have been updated"
else
- 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
+ puts_line "#{ChefUtils::Dist::Infra::PRODUCT} finished, #{@updated_resources}/#{total_resources} resources updated in #{pretty_elapsed_time}"
end
end
def run_failed(exception)
@end_time = Time.now
if Chef::Config[:why_run]
- puts_line "Chef Client failed. #{@updated_resources} resources would have been updated"
+ puts_line "#{ChefUtils::Dist::Infra::PRODUCT} failed. #{@updated_resources} resources would have been updated"
else
- puts_line "Chef Client failed. #{@updated_resources} resources updated in #{pretty_elapsed_time}"
- if total_audits > 0
- puts_line " #{successful_audits} controls succeeded"
- end
+ puts_line "#{ChefUtils::Dist::Infra::PRODUCT} failed. #{@updated_resources} resources updated in #{pretty_elapsed_time}"
end
end
# Called right after ohai runs.
- def ohai_completed(node)
- end
+ def ohai_completed(node); end
# Already have a client key, assuming this node has registered.
- def skipping_registration(node_name, config)
- end
+ def skipping_registration(node_name, config); end
# About to attempt to register as +node_name+
def registration_start(node_name, config)
puts_line "Creating a new client identity for #{node_name} using the validator key."
end
- def registration_completed
- end
+ def registration_completed; end
- def node_load_start(node_name, config)
- end
+ def node_load_start(node_name, config); end
# Failed to load node data from the server
def node_load_failed(node_name, exception, config)
@@ -125,8 +115,7 @@ class Chef
# Default and override attrs from roles have been computed, but not yet applied.
# Normal attrs from JSON have been added to the node.
- def node_load_completed(node, expanded_run_list, config)
- end
+ 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"]}'"
@@ -144,22 +133,18 @@ class Chef
end
# Called when the cookbook collection is returned from the server.
- def cookbook_resolution_complete(cookbook_collection)
- end
+ def cookbook_resolution_complete(cookbook_collection); end
# Called before unneeded cookbooks are removed
- def cookbook_clean_start
- end
+ def cookbook_clean_start; end
# Called after the file at +path+ is removed. It may be removed if the
# cookbook containing it was removed from the run list, or if the file was
# removed from the cookbook.
- def removed_cookbook_file(path)
- end
+ def removed_cookbook_file(path); end
# Called when cookbook cleaning is finished.
- def cookbook_clean_complete
- end
+ def cookbook_clean_complete; end
# Called before cookbook sync starts
def cookbook_sync_start(cookbook_count)
@@ -173,8 +158,7 @@ class Chef
end
# Called when an individual file in a cookbook has been updated
- def updated_cookbook_file(cookbook_name, path)
- end
+ def updated_cookbook_file(cookbook_name, path); end
# Called after all cookbooks have been sync'd.
def cookbook_sync_complete
@@ -213,12 +197,10 @@ class Chef
end
# Called after a file in a cookbook is loaded.
- def file_loaded(path)
- end
+ def file_loaded(path); end
# Called when recipes have been loaded.
- def recipe_load_complete
- end
+ def recipe_load_complete; end
# Called before convergence starts
def converge_start(run_context)
@@ -235,37 +217,6 @@ class Chef
converge_complete
end
- # Called before audit phase starts
- def audit_phase_start(run_status)
- puts_line "Starting audit phase"
- end
-
- def audit_phase_complete(audit_output)
- puts_line audit_output
- puts_line "Auditing complete"
- end
-
- def audit_phase_failed(error, audit_output)
- puts_line audit_output
- puts_line ""
- puts_line "Audit phase exception:"
- indent
- puts_line "#{error.message}"
- if error.backtrace
- error.backtrace.each do |l|
- puts_line l
- end
- end
- end
-
- def control_example_success(control_group_name, example_data)
- @successful_audits += 1
- end
-
- def control_example_failure(control_group_name, example_data, error)
- @failed_audits += 1
- end
-
# Called before action is executed on a resource.
def resource_action_start(resource, action, notification_type = nil, notifier = nil)
if resource.cookbook_name && resource.recipe_name
@@ -280,17 +231,17 @@ class Chef
@current_recipe = resource_recipe
indent
end
- # TODO: info about notifies
- start_line "* #{resource} action #{action}", :stream => resource
+ # @todo info about notifies
+ start_line "* #{resource} action #{action}", stream: resource
indent
end
def resource_update_progress(resource, current, total, interval)
- @progress[resource] ||= 0
+ @progress[resource] ||= -1
- percent_complete = (current.to_f / total.to_f * 100).to_i
+ percent_complete = (current.to_f / total.to_f * 100).to_i unless total.to_f == 0.0
- if percent_complete > @progress[resource]
+ if percent_complete && percent_complete > @progress[resource]
@progress[resource] = percent_complete
@@ -301,8 +252,7 @@ class Chef
end
# Called when a resource fails, but will retry.
- def resource_failed_retriable(resource, action, retry_count, exception)
- end
+ 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)
@@ -314,28 +264,26 @@ class Chef
def resource_skipped(resource, action, conditional)
@skipped_resources += 1
# TODO: more info about conditional
- puts " (skipped due to #{conditional.short_description})", :stream => resource
+ puts " (skipped due to #{conditional.short_description})", stream: resource
unindent
end
# Called after #load_current_resource has run.
- def resource_current_state_loaded(resource, action, current_resource)
- end
+ def resource_current_state_loaded(resource, action, current_resource); end
# Called when a resource has no converge actions, e.g., it was already correct.
def resource_up_to_date(resource, action)
@up_to_date_resources += 1
- puts " (up to date)", :stream => resource
+ puts " (up to date)", stream: resource unless resource.suppress_up_to_date_messages?
unindent
end
def resource_bypassed(resource, action, provider)
- puts " (Skipped: whyrun not supported by provider #{provider.class.name})", :stream => resource
+ puts " (Skipped: whyrun not supported by provider #{provider.class.name})", stream: resource
unindent
end
- def output_record(line)
- end
+ def output_record(line); end
# Called when a change has been made to a resource. May be called multiple
# times per resource, e.g., a file may have its content updated, and then
@@ -344,10 +292,11 @@ class Chef
prefix = Chef::Config[:why_run] ? "Would " : ""
Array(update).each do |line|
next if line.nil?
+
output_record line
- if line.kind_of? String
+ if line.is_a? String
start_line "- #{prefix}#{line}", :green
- elsif line.kind_of? Array
+ elsif line.is_a? Array
# Expanded output - delta
# @todo should we have a resource_update_delta callback?
line.each do |detail|
@@ -371,7 +320,7 @@ class Chef
end
def stream_output(stream, output, options = {})
- print(output, { :stream => stream }.merge(options))
+ print(output, { stream: stream }.merge(options))
end
# Called before handlers run
@@ -396,6 +345,7 @@ class Chef
# in whyrun mode, in order to allow execution to continue
def whyrun_assumption(action, resource, message)
return unless message
+
[ message ].flatten.each do |line|
start_line("* #{line}", :yellow)
end
@@ -404,20 +354,22 @@ class Chef
# Called when an assertion declared by a provider fails
def provider_requirement_failed(action, resource, exception, message)
return unless message
+
color = Chef::Config[:why_run] ? :yellow : :red
[ message ].flatten.each do |line|
start_line("* #{line}", color)
end
end
- def deprecation(message, location = caller(2..2)[0])
+ # (see Base#deprecation)
+ def deprecation(deprecation, _location = nil)
if Chef::Config[:treat_deprecation_warnings_as_errors]
super
+ elsif !deprecation.silenced?
+ # Save non-silenced deprecations to the screen until the end.
+ deprecations[deprecation.message] ||= { url: deprecation.url, locations: Set.new }
+ deprecations[deprecation.message][:locations] << deprecation.location
end
-
- # Save deprecations to the screen until the end
- deprecations[message] ||= Set.new
- deprecations[message] << location
end
def indent
diff --git a/lib/chef/formatters/error_description.rb b/lib/chef/formatters/error_description.rb
index 8d7f940181..dd64d37c5c 100644
--- a/lib/chef/formatters/error_description.rb
+++ b/lib/chef/formatters/error_description.rb
@@ -1,7 +1,7 @@
#
# Author:: Tyler Cloke (<tyler@chef.io>)
#
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,6 +17,8 @@
# limitations under the License.
#
+require_relative "../version"
+
class Chef
module Formatters
# == Formatters::ErrorDescription
@@ -45,10 +47,10 @@ class Chef
display_section(heading, text, out)
end
end
- display_section("Platform:", RUBY_PLATFORM, out)
+ display_section("System Info:", error_context_info, out)
end
- def for_json()
+ def for_json
{
"title" => @title,
"sections" => @sections,
@@ -64,6 +66,21 @@ class Chef
out.puts "\n"
end
+ def error_context_info
+ context_info = { chef_version: Chef::VERSION }
+ if Chef.node
+ context_info[:platform] = Chef.node["platform"]
+ context_info[:platform_version] = Chef.node["platform_version"]
+ end
+ # A string like "ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]"
+ context_info[:ruby] = RUBY_DESCRIPTION
+ # The argv[0] value.
+ context_info[:program_name] = $PROGRAM_NAME
+ # This is kind of wonky but it's the only way to get the entry path script.
+ context_info[:executable] = File.realpath(caller.last[/^(.*):\d+:in /, 1])
+ context_info.map { |k, v| "#{k}=#{v}" }.join("\n")
+ end
+
end
end
end
diff --git a/lib/chef/formatters/error_inspectors.rb b/lib/chef/formatters/error_inspectors.rb
index 9221ecda4d..e83d527e05 100644
--- a/lib/chef/formatters/error_inspectors.rb
+++ b/lib/chef/formatters/error_inspectors.rb
@@ -1,10 +1,10 @@
-require "chef/formatters/error_inspectors/node_load_error_inspector"
-require "chef/formatters/error_inspectors/registration_error_inspector"
-require "chef/formatters/error_inspectors/compile_error_inspector"
-require "chef/formatters/error_inspectors/resource_failure_inspector"
-require "chef/formatters/error_inspectors/run_list_expansion_error_inspector"
-require "chef/formatters/error_inspectors/cookbook_resolve_error_inspector"
-require "chef/formatters/error_inspectors/cookbook_sync_error_inspector"
+require_relative "error_inspectors/node_load_error_inspector"
+require_relative "error_inspectors/registration_error_inspector"
+require_relative "error_inspectors/compile_error_inspector"
+require_relative "error_inspectors/resource_failure_inspector"
+require_relative "error_inspectors/run_list_expansion_error_inspector"
+require_relative "error_inspectors/cookbook_resolve_error_inspector"
+require_relative "error_inspectors/cookbook_sync_error_inspector"
class Chef
module Formatters
diff --git a/lib/chef/formatters/error_inspectors/api_error_formatting.rb b/lib/chef/formatters/error_inspectors/api_error_formatting.rb
index 2415d0f4bb..ee4583c89b 100644
--- a/lib/chef/formatters/error_inspectors/api_error_formatting.rb
+++ b/lib/chef/formatters/error_inspectors/api_error_formatting.rb
@@ -1,6 +1,6 @@
#--
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,90 +16,92 @@
# limitations under the License.
#
-require "chef/http/authenticator"
+require_relative "../../http/authenticator"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+require "timeout" unless defined?(Timeout)
class Chef
module Formatters
module APIErrorFormatting
- NETWORK_ERROR_CLASSES = [Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, SocketError]
+ NETWORK_ERROR_CLASSES = [Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, SocketError].freeze
def describe_network_errors(error_description)
- error_description.section("Networking Error:", <<-E)
-#{exception.message}
-
-Your chef_server_url may be misconfigured, or the network could be down.
-E
- error_description.section("Relevant Config Settings:", <<-E)
-chef_server_url "#{server_url}"
-E
+ error_description.section("Networking Error:", <<~E)
+ #{exception.message}
+
+ Your chef_server_url may be misconfigured, or the network could be down.
+ E
+ error_description.section("Relevant Config Settings:", <<~E)
+ chef_server_url "#{server_url}"
+ E
end
def describe_eof_error(error_description)
- error_description.section("Authentication Error:", <<-E)
-Received an EOF on transport socket. This almost always indicates a network
-error external to chef-client. Some causes include:
+ error_description.section("Authentication Error:", <<~E)
+ Received an EOF on transport socket. This almost always indicates a network
+ error external to #{ChefUtils::Dist::Infra::CLIENT}. Some causes include:
- - Blocking ICMP Dest Unreachable (breaking Path MTU Discovery)
- - IPsec or VPN tunnelling / TCP Encapsulation MTU issues
- - Jumbo frames configured only on one side (breaking Path MTU)
- - Jumbo frames configured on a LAN that does not support them
- - Proxies or Load Balancers breaking large POSTs
- - Broken TCP offload in network drivers/hardware
+ - Blocking ICMP Dest Unreachable (breaking Path MTU Discovery)
+ - IPsec or VPN tunnelling / TCP Encapsulation MTU issues
+ - Jumbo frames configured only on one side (breaking Path MTU)
+ - Jumbo frames configured on a LAN that does not support them
+ - Proxies or Load Balancers breaking large POSTs
+ - Broken TCP offload in network drivers/hardware
-Try sending large pings to the destination:
+ Try sending large pings to the destination:
- windows: ping server.example.com -f -l 9999
- unix: ping server.example.com -s 9999
+ windows: ping server.example.com -f -l 9999
+ unix: ping server.example.com -s 9999
-Try sending large POSTs to the destination (any HTTP code returned is success):
+ Try sending large POSTs to the destination (any HTTP code returned is success):
- e.g.: curl http://server.example.com/`printf '%*s' 9999 '' | tr ' ' 'a'`
+ e.g.: curl http://server.example.com/`printf '%*s' 9999 '' | tr ' ' 'a'`
-Try disabling TCP Offload Engines (TOE) in your ethernet drivers.
+ Try disabling TCP Offload Engines (TOE) in your ethernet drivers.
- windows:
- Disable-NetAdapterChecksumOffload * -TcpIPv4 -UdpIPv4 -IpIPv4 -NoRestart
- Disable-NetAdapterLso * -IPv4 -NoRestart
- Set-NetAdapterAdvancedProperty * -DisplayName "Large Receive Offload (IPv4)" -DisplayValue Disabled –NoRestart
- Restart-NetAdapter *
- unix(bash):
- for i in rx tx sg tso ufo gso gro lro rxvlan txvlan rxhash; do /sbin/ethtool -K eth0 $i off; done
+ windows:
+ Disable-NetAdapterChecksumOffload * -TcpIPv4 -UdpIPv4 -IpIPv4 -NoRestart
+ Disable-NetAdapterLso * -IPv4 -NoRestart
+ Set-NetAdapterAdvancedProperty * -DisplayName "Large Receive Offload (IPv4)" -DisplayValue Disabled –NoRestart
+ Restart-NetAdapter *
+ unix(bash):
+ for i in rx tx sg tso ufo gso gro lro rxvlan txvlan rxhash; do /sbin/ethtool -K eth0 $i off; done
-In some cases the underlying virtualization layer (Xen, VMware, KVM, Hyper-V, etc) may have
-broken virtual networking code.
+ In some cases the underlying virtualization layer (Xen, VMware, KVM, Hyper-V, etc) may have
+ broken virtual networking code.
E
end
def describe_401_error(error_description)
if clock_skew?
- error_description.section("Authentication Error:", <<-E)
-Failed to authenticate to the chef server (http 401).
-The request failed because your clock has drifted by more than 15 minutes.
-Syncing your clock to an NTP Time source should resolve the issue.
-E
+ error_description.section("Authentication Error:", <<~E)
+ Failed to authenticate to the chef server (http 401).
+ The request failed because your clock has drifted by more than 15 minutes.
+ Syncing your clock to an NTP Time source should resolve the issue.
+ E
else
- error_description.section("Authentication Error:", <<-E)
-Failed to authenticate to the chef server (http 401).
-E
+ error_description.section("Authentication Error:", <<~E)
+ Failed to authenticate to the chef server (http 401).
+ E
error_description.section("Server Response:", format_rest_error)
- error_description.section("Relevant Config Settings:", <<-E)
-chef_server_url "#{server_url}"
-node_name "#{username}"
-client_key "#{api_key}"
-
-If these settings are correct, your client_key may be invalid, or
-you may have a chef user with the same client name as this node.
-E
+ error_description.section("Relevant Config Settings:", <<~E)
+ chef_server_url "#{server_url}"
+ node_name "#{username}"
+ client_key "#{api_key}"
+
+ If these settings are correct, your client_key may be invalid, or
+ you may have a chef user with the same client name as this node.
+ E
end
end
def describe_400_error(error_description)
- error_description.section("Invalid Request Data:", <<-E)
-The data in your request was invalid (HTTP 400).
-E
+ error_description.section("Invalid Request Data:", <<~E)
+ The data in your request was invalid (HTTP 400).
+ E
error_description.section("Server Response:", format_rest_error)
end
@@ -110,26 +112,26 @@ E
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
+ error_description.section("Incompatible server API version:", <<~E)
+ This version of the API that this request specified is not supported by the 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}.
+ #{ChefUtils::Dist::Infra::PRODUCT} just made a request with an API version of #{client_api_version}.
+ Please either update your #{ChefUtils::Dist::Infra::PRODUCT} or the 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.
-E
+ error_description.section("Unknown Server Error:", <<~E)
+ The server had a fatal error attempting to load the node data.
+ E
error_description.section("Server Response:", format_rest_error)
end
def describe_503_error(error_description)
- error_description.section("Server Unavailable", "The Chef Server is temporarily unavailable")
+ error_description.section("Server Unavailable", "The #{ChefUtils::Dist::Server::PRODUCT} is temporarily unavailable")
error_description.section("Server Response:", format_rest_error)
end
@@ -172,11 +174,10 @@ E
# .../lib/ruby/1.9.1/net/http.rb:2709:in `read_body'
# .../lib/ruby/1.9.1/net/http.rb:2736:in `body'
# .../lib/chef/formatters/error_inspectors/api_error_formatting.rb:91:in `rescue in format_rest_error'
- begin
- exception.response.body
- rescue Exception
- "Cannot fetch the contents of the response."
- end
+
+ exception.response.body
+ rescue Exception
+ "Cannot fetch the contents of the response."
end
end
diff --git a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
index d5ed69a83d..e42340ff3a 100644
--- a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
@@ -1,6 +1,6 @@
#--
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -41,7 +41,7 @@ class Chef
if found_error_in_cookbooks?
traceback = filtered_bt.map { |line| " #{line}" }.join("\n")
- error_description.section("Cookbook Trace:", traceback)
+ error_description.section("Cookbook Trace: (most recent call first)", traceback)
error_description.section("Relevant File Content:", context)
end
@@ -108,21 +108,21 @@ 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.trace("Backtrace entry for compile error: '#{bt_entry}'")
bt_entry
end
end
def culprit_line
@culprit_line ||= begin
- line_number = culprit_backtrace_entry[/^(?:.\:)?[^:]+:([\d]+)/, 1].to_i
- Chef::Log.debug("Line number of compile error: '#{line_number}'")
+ line_number = culprit_backtrace_entry[/^(?:.\:)?[^:]+:(\d+)/, 1].to_i
+ Chef::Log.trace("Line number of compile error: '#{line_number}'")
line_number
end
end
def culprit_file
- @culprit_file ||= culprit_backtrace_entry[/^((?:.\:)?[^:]+):([\d]+)/, 1]
+ @culprit_file ||= culprit_backtrace_entry[/^((?:.\:)?[^:]+):(\d+)/, 1]
end
def filtered_bt
@@ -138,7 +138,7 @@ class Chef
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(",")}")
+ Chef::Log.trace("Filtered backtrace of compile error: #{r.join(",")}")
r
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 eb1aa629ff..b5029c082a 100644
--- a/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb
@@ -1,6 +1,6 @@
#--
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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/formatters/error_inspectors/api_error_formatting"
+require_relative "api_error_formatting"
class Chef
module Formatters
@@ -35,7 +35,7 @@ class Chef
def add_explanation(error_description)
case exception
- when Net::HTTPServerException, Net::HTTPFatalError
+ when Net::HTTPClientException, Net::HTTPFatalError
humanize_http_exception(error_description)
when EOFError
describe_eof_error(error_description)
@@ -56,13 +56,13 @@ class Chef
# TODO: we're rescuing errors from Node.find_or_create
# * could be no write on nodes container
# * could be no read on the node
- error_description.section("Authorization Error", <<-E)
-This client is not authorized to read some of the information required to
-access its cookbooks (HTTP 403).
+ error_description.section("Authorization Error", <<~E)
+ This client is not authorized to read some of the information required to
+ access its cookbooks (HTTP 403).
-To access its cookbooks, a client needs to be able to read its environment and
-all of the cookbooks in its expanded run list.
-E
+ To access its cookbooks, a client needs to be able to read its environment and
+ all of the cookbooks in its expanded run list.
+ E
error_description.section("Expanded Run List:", expanded_run_list_ul)
error_description.section("Server Response:", format_rest_error)
when Net::HTTPPreconditionFailed
@@ -115,12 +115,12 @@ E
explanation << "Error message: #{error_reasons["message"]}\n"
end
- explanation << <<EOM
-You might be able to resolve this issue with:
- 1-) Removing cookbook versions that depend on deleted cookbooks.
- 2-) Removing unused cookbook versions.
- 3-) Pinning exact cookbook versions using environments.
-EOM
+ explanation << <<~EOM
+ You might be able to resolve this issue with:
+ 1-) Removing cookbook versions that depend on deleted cookbooks.
+ 2-) Removing unused cookbook versions.
+ 3-) Pinning exact cookbook versions using environments.
+ EOM
error_description.section("Cookbook dependency resolution error:", explanation)
end
@@ -143,14 +143,15 @@ EOM
# "{\"error\":[\"{\\\"non_existent_cookbooks\\\":[\\\"nope\\\"],\\\"cookbooks_with_no_versions\\\":[],\\\"message\\\":\\\"Run list contains invalid items: no such cookbook nope.\\\"}\"]}"
wrapped_error_message = attempt_json_parse(exception.response.body)
- unless wrapped_error_message.kind_of?(Hash) && wrapped_error_message.key?("error")
+ unless wrapped_error_message.is_a?(Hash) && wrapped_error_message.key?("error")
return wrapped_error_message.to_s
end
error_description = Array(wrapped_error_message["error"]).first
- if error_description.kind_of?(Hash)
+ if error_description.is_a?(Hash)
return error_description
end
+
attempt_json_parse(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 3bd9b419fa..45b550e5ab 100644
--- a/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb
@@ -1,6 +1,6 @@
#--
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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/formatters/error_inspectors/api_error_formatting"
+require_relative "api_error_formatting"
class Chef
module Formatters
@@ -41,7 +41,7 @@ class Chef
def add_explanation(error_description)
case exception
- when Net::HTTPServerException, Net::HTTPFatalError
+ when Net::HTTPClientException, Net::HTTPFatalError
humanize_http_exception(error_description)
when EOFError
describe_eof_error(error_description)
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 c52dad4c09..7e904c9ee2 100644
--- a/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb
@@ -1,6 +1,6 @@
#--
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,7 +16,8 @@
# limitations under the License.
#
-require "chef/formatters/error_inspectors/api_error_formatting"
+require_relative "api_error_formatting"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
module Formatters
@@ -40,16 +41,16 @@ class Chef
def add_explanation(error_description)
case exception
- when Net::HTTPServerException, Net::HTTPFatalError
+ when Net::HTTPClientException, Net::HTTPFatalError
humanize_http_exception(error_description)
when Chef::Exceptions::PrivateKeyMissing
- error_description.section("Private Key Not Found:", <<-E)
-Your private key could not be loaded. If the key file exists, ensure that it is
-readable by chef-client.
-E
- error_description.section("Relevant Config Settings:", <<-E)
-client_key "#{api_key}"
-E
+ error_description.section("Private Key Not Found:", <<~E)
+ Your private key could not be loaded. If the key file exists, ensure that it is
+ readable by #{ChefUtils::Dist::Infra::PRODUCT}.
+ E
+ error_description.section("Relevant Config Settings:", <<~E)
+ client_key "#{api_key}"
+ E
when EOFError
describe_eof_error(error_description)
when *NETWORK_ERROR_CLASSES
@@ -69,14 +70,14 @@ E
# TODO: we're rescuing errors from Node.find_or_create
# * could be no write on nodes container
# * could be no read on the node
- error_description.section("Authorization Error", <<-E)
-Your client is not authorized to load the node data (HTTP 403).
-E
+ error_description.section("Authorization Error", <<~E)
+ Your client is not authorized to load the node data (HTTP 403).
+ E
error_description.section("Server Response:", format_rest_error)
- error_description.section("Possible Causes:", <<-E)
-* Your client (#{username}) may have misconfigured authorization permissions.
-E
+ error_description.section("Possible Causes:", <<~E)
+ * Your client (#{username}) may have misconfigured authorization permissions.
+ E
when Net::HTTPBadRequest
describe_400_error(error_description)
when Net::HTTPNotFound
@@ -97,12 +98,12 @@ E
# one, e.g., PUT http://wrong.url/nodes/node-name becomes a GET after a
# redirect.
def describe_404_error(error_description)
- error_description.section("Resource Not Found:", <<-E)
-The server returned a HTTP 404. This usually indicates that your chef_server_url is incorrect.
-E
- error_description.section("Relevant Config Settings:", <<-E)
-chef_server_url "#{server_url}"
-E
+ error_description.section("Resource Not Found:", <<~E)
+ The #{ChefUtils::Dist::Server::PRODUCT} returned a HTTP 404. This usually indicates that your chef_server_url is incorrect.
+ E
+ error_description.section("Relevant Config Settings:", <<~E)
+ chef_server_url "#{server_url}"
+ E
end
def username
diff --git a/lib/chef/formatters/error_inspectors/registration_error_inspector.rb b/lib/chef/formatters/error_inspectors/registration_error_inspector.rb
index c7c1454311..4897ac6e1b 100644
--- a/lib/chef/formatters/error_inspectors/registration_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/registration_error_inspector.rb
@@ -1,7 +1,8 @@
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
class Chef
module Formatters
module ErrorInspectors
-
# == RegistrationErrorInspector
# Wraps exceptions that occur during the client registration process and
# suggests possible causes.
@@ -23,30 +24,30 @@ class Chef
def add_explanation(error_description)
case exception
- when Net::HTTPServerException, Net::HTTPFatalError
+ when Net::HTTPClientException, Net::HTTPFatalError
humanize_http_exception(error_description)
when Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, SocketError
- error_description.section("Network Error:", <<-E)
-There was a network error connecting to the Chef Server:
-#{exception.message}
-E
- error_description.section("Relevant Config Settings:", <<-E)
-chef_server_url "#{server_url}"
+ error_description.section("Network Error:", <<~E)
+ There was a network error connecting to the #{ChefUtils::Dist::Server::PRODUCT}:
+ #{exception.message}
+ E
+ error_description.section("Relevant Config Settings:", <<~E)
+ chef_server_url "#{server_url}"
-If your chef_server_url is correct, your network could be down.
-E
+ If your chef_server_url is correct, your network could be down.
+ E
when Chef::Exceptions::PrivateKeyMissing
- error_description.section("Private Key Not Found:", <<-E)
-Your private key could not be loaded. If the key file exists, ensure that it is
-readable by chef-client.
-E
- error_description.section("Relevant Config Settings:", <<-E)
-validation_key "#{api_key}"
-E
+ error_description.section("Private Key Not Found:", <<~E)
+ Your private key could not be loaded. If the key file exists, ensure that it is
+ readable by #{ChefUtils::Dist::Infra::PRODUCT}.
+ E
+ error_description.section("Relevant Config Settings:", <<~E)
+ validation_key "#{api_key}"
+ E
when Chef::Exceptions::InvalidRedirect
- error_description.section("Invalid Redirect:", <<-E)
-Change your server location in client.rb to the server's FQDN to avoid unwanted redirections.
-E
+ error_description.section("Invalid Redirect:", <<~E)
+ Change your #{ChefUtils::Dist::Server::PRODUCT} location in client.rb to the #{ChefUtils::Dist::Server::PRODUCT}'s FQDN to avoid unwanted redirections.
+ E
when EOFError
describe_eof_error(error_description)
else
@@ -59,54 +60,54 @@ E
case response
when Net::HTTPUnauthorized
if clock_skew?
- error_description.section("Authentication Error:", <<-E)
-Failed to authenticate to the chef server (http 401).
-The request failed because your clock has drifted by more than 15 minutes.
-Syncing your clock to an NTP Time source should resolve the issue.
-E
+ error_description.section("Authentication Error:", <<~E)
+ Failed to authenticate to the #{ChefUtils::Dist::Server::PRODUCT} (http 401).
+ The request failed because your clock has drifted by more than 15 minutes.
+ Syncing your clock to an NTP Time source should resolve the issue.
+ E
else
- error_description.section("Authentication Error:", <<-E)
-Failed to authenticate to the chef server (http 401).
-E
+ error_description.section("Authentication Error:", <<~E)
+ Failed to authenticate to the #{ChefUtils::Dist::Server::PRODUCT} (http 401).
+ E
error_description.section("Server Response:", format_rest_error)
- error_description.section("Relevant Config Settings:", <<-E)
-chef_server_url "#{server_url}"
-validation_client_name "#{username}"
-validation_key "#{api_key}"
+ error_description.section("Relevant Config Settings:", <<~E)
+ chef_server_url "#{server_url}"
+ validation_client_name "#{username}"
+ validation_key "#{api_key}"
-If these settings are correct, your validation_key may be invalid.
-E
+ If these settings are correct, your validation_key may be invalid.
+ E
end
when Net::HTTPForbidden
- error_description.section("Authorization Error:", <<-E)
-Your validation client is not authorized to create the client for this node (HTTP 403).
-E
- error_description.section("Possible Causes:", <<-E)
-* There may already be a client named "#{config[:node_name]}"
-* Your validation client (#{username}) may have misconfigured authorization permissions.
-E
+ error_description.section("Authorization Error:", <<~E)
+ Your validation client is not authorized to create the client for this node on the #{ChefUtils::Dist::Server::PRODUCT} (HTTP 403).
+ E
+ error_description.section("Possible Causes:", <<~E)
+ * There may already be a client named "#{config[:node_name]}"
+ * Your validation client (#{username}) may have misconfigured authorization permissions.
+ E
when Net::HTTPBadRequest
- error_description.section("Invalid Request Data:", <<-E)
-The data in your request was invalid (HTTP 400).
-E
+ error_description.section("Invalid Request Data:", <<~E)
+ The data in your request was invalid (HTTP 400).
+ E
error_description.section("Server Response:", format_rest_error)
when Net::HTTPNotFound
- error_description.section("Resource Not Found:", <<-E)
-The server returned a HTTP 404. This usually indicates that your chef_server_url is incorrect.
-E
- error_description.section("Relevant Config Settings:", <<-E)
-chef_server_url "#{server_url}"
-E
+ error_description.section("Resource Not Found:", <<~E)
+ The #{ChefUtils::Dist::Server::PRODUCT} returned a HTTP 404. This usually indicates that your chef_server_url configuration is incorrect.
+ 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.
-E
+ error_description.section("Unknown Server Error:", <<~E)
+ The server had a fatal error attempting to load the node data.
+ E
error_description.section("Server Response:", format_rest_error)
when Net::HTTPBadGateway, Net::HTTPServiceUnavailable
- error_description.section("Server Unavailable", "The Chef Server is temporarily unavailable")
+ error_description.section("Server Unavailable", "The #{ChefUtils::Dist::Server::PRODUCT} is temporarily unavailable")
error_description.section("Server Response:", format_rest_error)
else
error_description.section("Unexpected API Request Failure:", format_rest_error)
@@ -114,13 +115,13 @@ E
end
def username
- #config[:node_name]
+ # config[:node_name]
config[:validation_client_name]
end
def api_key
config[:validation_key]
- #config[:client_key]
+ # config[:client_key]
end
def server_url
diff --git a/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb b/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb
index 94ecce88de..905a438f56 100644
--- a/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb
@@ -1,7 +1,7 @@
#--
# Author:: Daniel DeLeo (<dan@chef.io>)
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,6 +16,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+require "chef-utils" unless defined?(ChefUtils::CANARY)
class Chef
module Formatters
@@ -36,14 +37,14 @@ class Chef
error_description.section(exception.class.name, exception.message)
unless filtered_bt.empty?
- error_description.section("Cookbook Trace:", filtered_bt.join("\n"))
+ error_description.section("Cookbook Trace: (most recent call first)", filtered_bt.join("\n"))
end
unless dynamic_resource?
error_description.section("Resource Declaration:", resource.sensitive ? "suppressed sensitive resource output" : recipe_snippet)
end
- error_description.section("Compiled Resource:", "#{resource.to_text}")
+ error_description.section("Compiled Resource:", (resource.to_text).to_s)
# Template errors get wrapped in an exception class that can show the relevant template code,
# so add them to the error output.
@@ -51,20 +52,22 @@ class Chef
error_description.section("Template Context:", "#{exception.source_location}\n#{exception.source_listing}")
end
- if Chef::Platform.windows?
- require "chef/win32/security"
+ if ChefUtils.windows?
+ require_relative "../../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.")
+ unless Chef::ReservedNames::Win32::Security.has_admin_privileges?
+ error_description.section("Missing Windows Admin Privileges", "#{ChefUtils::Dist::Infra::CLIENT} doesn't have administrator privileges. This can be a possible reason for the resource failure.")
end
end
end
def recipe_snippet
return nil if dynamic_resource?
+
@snippet ||= begin
if (file = parse_source) && (line = parse_line(file))
return nil unless ::File.exists?(file)
+
lines = IO.readlines(file)
relevant_lines = ["# In #{file}\n\n"]
@@ -76,8 +79,8 @@ class Chef
loop do
# low rent parser. try to gracefully handle nested blocks in resources
- nesting += 1 if lines[current_line] =~ /[\s]+do[\s]*/
- nesting -= 1 if lines[current_line] =~ /end[\s]*$/
+ nesting += 1 if /\s+do\s*/.match?(lines[current_line])
+ nesting -= 1 if /end\s*$/.match?(lines[current_line])
relevant_lines << format_line(current_line, lines[current_line])
@@ -111,11 +114,11 @@ class Chef
end
def parse_source
- resource.source_line[/^(([\w]:)?[^:]+):([\d]+)/, 1]
+ resource.source_line[/^((\w:)?[^:]+):(\d+)/, 1]
end
def parse_line(source)
- resource.source_line[/^#{Regexp.escape(source)}:([\d]+)/, 1].to_i
+ resource.source_line[/^#{Regexp.escape(source)}:(\d+)/, 1].to_i
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 e94b347378..6e452c959b 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
@@ -1,7 +1,7 @@
#--
# Author:: Daniel DeLeo (<dan@chef.io>)
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,7 +17,8 @@
# limitations under the License.
#
-require "chef/formatters/error_inspectors/api_error_formatting"
+require_relative "api_error_formatting"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
module Formatters
@@ -36,12 +37,12 @@ class Chef
def add_explanation(error_description)
case exception
when Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, SocketError
- error_description.section("Networking Error:", <<-E)
-#{exception.message}
+ error_description.section("Networking Error:", <<~E)
+ #{exception.message}
-Your chef_server_url may be misconfigured, or the network could be down.
-E
- when Net::HTTPServerException, Net::HTTPFatalError
+ Your chef_server_url may be misconfigured, or the network could be down.
+ E
+ when Net::HTTPClientException, Net::HTTPFatalError
humanize_http_exception(error_description)
when Chef::Exceptions::MissingRole
describe_missing_role(error_description)
@@ -76,39 +77,39 @@ E
response = exception.response
case response
when Net::HTTPUnauthorized
- error_description.section("Authentication Error:", <<-E)
-Failed to authenticate to the chef server (http 401).
-E
+ error_description.section("Authentication Error:", <<~E)
+ Failed to authenticate to the #{ChefUtils::Dist::Server::PRODUCT} (http 401).
+ E
error_description.section("Server Response:", format_rest_error)
- error_description.section("Relevant Config Settings:", <<-E)
-chef_server_url "#{server_url}"
-node_name "#{username}"
-client_key "#{api_key}"
+ error_description.section("Relevant Config Settings:", <<~E)
+ chef_server_url "#{server_url}"
+ node_name "#{username}"
+ client_key "#{api_key}"
-If these settings are correct, your client_key may be invalid.
-E
+ If these settings are correct, your client_key may be invalid.
+ E
when Net::HTTPForbidden
# TODO: we're rescuing errors from Node.find_or_create
# * could be no write on nodes container
# * could be no read on the node
- error_description.section("Authorization Error", <<-E)
-Your client is not authorized to load one or more of your roles (HTTP 403).
-E
+ error_description.section("Authorization Error", <<~E)
+ Your client is not authorized to load one or more of your roles (HTTP 403).
+ E
error_description.section("Server Response:", format_rest_error)
- error_description.section("Possible Causes:", <<-E)
-* Your client (#{username}) may have misconfigured authorization permissions.
-E
+ 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.
-E
+ error_description.section("Unknown Server Error:", <<~E)
+ The server had a fatal error attempting to load a role.
+ E
error_description.section("Server Response:", format_rest_error)
when Net::HTTPBadGateway, Net::HTTPServiceUnavailable
- error_description.section("Server Unavailable", "The Chef Server is temporarily unavailable")
+ error_description.section("Server Unavailable", "The #{ChefUtils::Dist::Server::PRODUCT} is temporarily unavailable")
error_description.section("Server Response:", format_rest_error)
else
error_description.section("Unexpected API Request Failure:", format_rest_error)
diff --git a/lib/chef/formatters/error_mapper.rb b/lib/chef/formatters/error_mapper.rb
index 4786a20465..b0d25149dd 100644
--- a/lib/chef/formatters/error_mapper.rb
+++ b/lib/chef/formatters/error_mapper.rb
@@ -1,6 +1,6 @@
#--
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,7 +30,7 @@ class Chef
headline = "Chef encountered an error attempting to create the client \"#{node_name}\""
description = ErrorDescription.new(headline)
error_inspector.add_explanation(description)
- return description
+ description
end
def self.node_load_failed(node_name, exception, config)
@@ -38,7 +38,7 @@ class Chef
headline = "Chef encountered an error attempting to load the node data for \"#{node_name}\""
description = ErrorDescription.new(headline)
error_inspector.add_explanation(description)
- return description
+ description
end
def self.run_list_expand_failed(node, exception)
@@ -46,7 +46,7 @@ class Chef
headline = "Error expanding the run_list:"
description = ErrorDescription.new(headline)
error_inspector.add_explanation(description)
- return description
+ description
end
def self.cookbook_resolution_failed(expanded_run_list, exception)
@@ -54,7 +54,7 @@ class Chef
headline = "Error Resolving Cookbooks for Run List:"
description = ErrorDescription.new(headline)
error_inspector.add_explanation(description)
- return description
+ description
end
def self.cookbook_sync_failed(cookbooks, exception)
@@ -62,7 +62,7 @@ class Chef
headline = "Error Syncing Cookbooks:"
description = ErrorDescription.new(headline)
error_inspector.add_explanation(description)
- return description
+ description
end
def self.resource_failed(resource, action, exception)
@@ -70,7 +70,7 @@ class Chef
headline = "Error executing action `#{action}` on resource '#{resource}'"
description = ErrorDescription.new(headline)
error_inspector.add_explanation(description)
- return description
+ description
end
def self.file_load_failed(path, exception)
diff --git a/lib/chef/formatters/indentable_output_stream.rb b/lib/chef/formatters/indentable_output_stream.rb
index e5e84e0f65..4943041b37 100644
--- a/lib/chef/formatters/indentable_output_stream.rb
+++ b/lib/chef/formatters/indentable_output_stream.rb
@@ -17,37 +17,40 @@ class Chef
@semaphore = Mutex.new
end
- def highline
- @highline ||= begin
- require "highline"
- HighLine.new
+ # pastel.decorate is a lightweight replacement for highline.color
+ def pastel
+ @pastel ||= begin
+ require "pastel" unless defined?(Pastel)
+ Pastel.new
end
end
- # Print text. This will start a new line and indent if necessary
- # but will not terminate the line (future print and puts statements
- # will start off where this print left off).
- def color(string, *args)
- print(string, from_args(args))
- end
-
# Print the start of a new line. This will terminate any existing lines and
# cause indentation but will not move to the next line yet (future 'print'
# and 'puts' statements will stay on this line).
+ #
+ # @param string [String]
+ # @param args [Array<Hash,Symbol>]
def start_line(string, *args)
- print(string, from_args(args, :start_line => true))
+ print(string, from_args(args, start_line: true))
end
# Print a line. This will continue from the last start_line or print,
# or start a new line and indent if necessary.
+ #
+ # @param string [String]
+ # @param args [Array<Hash,Symbol>]
def puts(string, *args)
- print(string, from_args(args, :end_line => true))
+ print(string, from_args(args, end_line: true))
end
# Print an entire line from start to end. This will terminate any existing
# lines and cause indentation.
+ #
+ # @param string [String]
+ # @param args [Array<Hash,Symbol>]
def puts_line(string, *args)
- print(string, from_args(args, :start_line => true, :end_line => true))
+ print(string, from_args(args, start_line: true, end_line: true))
end
# Print a raw chunk
@@ -71,7 +74,7 @@ class Chef
#
# == Alternative
#
- # You may also call print('string', :red) (a list of colors a la Highline.color)
+ # You may also call print('string', :red) (https://github.com/piotrmurach/pastel#3-supported-colors)
def print(string, *args)
options = from_args(args)
@@ -100,10 +103,10 @@ class Chef
end
def from_args(colors, merge_options = {})
- if colors.size == 1 && colors[0].kind_of?(Hash)
+ if colors.size == 1 && colors[0].is_a?(Hash)
merge_options.merge(colors[0])
else
- merge_options.merge({ :colors => colors })
+ merge_options.merge({ colors: colors })
end
end
@@ -123,12 +126,12 @@ class Chef
indent_line(options)
# Note that the next line will need to be started
- if line[-1..-1] == "\n"
+ if line[-1..] == "\n"
@line_started = false
end
if Chef::Config[:color] && options[:colors]
- @out.print highline.color(line, *options[:colors])
+ @out.print pastel.decorate(line, *options[:colors])
else
@out.print line
end
@@ -142,14 +145,14 @@ class Chef
end
def indent_line(options)
- if !@line_started
+ unless @line_started
# Print indents. If there is a stream name, either print it (if we're
# switching streams) or print enough blanks to match
# the indents.
if options[:name]
if @current_stream != options[:stream]
- @out.print "#{(' ' * indent)}[#{options[:name]}] "
+ @out.print "#{(" " * indent)}[#{options[:name]}] "
else
@out.print " " * (indent + 3 + options[:name].size)
end
diff --git a/lib/chef/formatters/minimal.rb b/lib/chef/formatters/minimal.rb
index c8fc504eb0..6a067c4f86 100644
--- a/lib/chef/formatters/minimal.rb
+++ b/lib/chef/formatters/minimal.rb
@@ -1,4 +1,5 @@
-require "chef/formatters/base"
+require_relative "base"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
@@ -26,52 +27,47 @@ class Chef
end
# Called at the very start of a Chef Run
- def run_start(version)
- puts_line "Starting Chef Client, version #{version}"
+ def run_start(version, run_status)
+ puts_line "Starting #{ChefUtils::Dist::Infra::PRODUCT}, version #{version}"
+ puts_line "Patents: #{ChefUtils::Dist::Org::PATENTS}"
+ puts_line "Targeting node: #{Chef::Config.target_mode.host}" if Chef::Config.target_mode?
puts_line "OpenSSL FIPS 140 mode enabled" if Chef::Config[:fips]
end
# Called at the end of the Chef run.
def run_completed(node)
- puts "chef client finished, #{@updated_resources.size} resources updated"
+ puts "#{ChefUtils::Dist::Infra::PRODUCT} finished, #{@updated_resources.size} resources updated"
end
# called at the end of a failed run
def run_failed(exception)
- puts "chef client failed. #{@updated_resources.size} resources updated"
+ puts "#{ChefUtils::Dist::Infra::PRODUCT} failed. #{@updated_resources.size} resources updated"
end
# Called right after ohai runs.
- def ohai_completed(node)
- end
+ def ohai_completed(node); end
# Already have a client key, assuming this node has registered.
- def skipping_registration(node_name, config)
- end
+ def skipping_registration(node_name, config); end
# About to attempt to register as +node_name+
- def registration_start(node_name, config)
- end
+ def registration_start(node_name, config); end
- def registration_completed
- end
+ def registration_completed; end
# Failed to register this client with the server.
def registration_failed(node_name, exception, config)
super
end
- def node_load_start(node_name, config)
- end
+ def node_load_start(node_name, config); end
# Failed to load node data from the server
- def node_load_failed(node_name, exception, config)
- end
+ def node_load_failed(node_name, exception, config); end
# Default and override attrs from roles have been computed, but not yet applied.
# Normal attrs from JSON have been added to the node.
- def node_load_completed(node, expanded_run_list, config)
- end
+ def node_load_completed(node, expanded_run_list, config); end
# Called before the cookbook collection is fetched from the server.
def cookbook_resolution_start(expanded_run_list)
@@ -80,28 +76,23 @@ class Chef
# Called when there is an error getting the cookbook collection from the
# server.
- def cookbook_resolution_failed(expanded_run_list, exception)
- end
+ def cookbook_resolution_failed(expanded_run_list, exception); end
# Called when the cookbook collection is returned from the server.
- def cookbook_resolution_complete(cookbook_collection)
- end
+ def cookbook_resolution_complete(cookbook_collection); end
# Called before unneeded cookbooks are removed
#--
# TODO: Should be called in CookbookVersion.sync_cookbooks
- def cookbook_clean_start
- end
+ def cookbook_clean_start; end
# Called after the file at +path+ is removed. It may be removed if the
# cookbook containing it was removed from the run list, or if the file was
# removed from the cookbook.
- def removed_cookbook_file(path)
- end
+ def removed_cookbook_file(path); end
# Called when cookbook cleaning is finished.
- def cookbook_clean_complete
- end
+ def cookbook_clean_complete; end
# Called before cookbook sync starts
def cookbook_sync_start(cookbook_count)
@@ -114,8 +105,7 @@ class Chef
end
# Called when an individual file in a cookbook has been updated
- def updated_cookbook_file(cookbook_name, path)
- end
+ def updated_cookbook_file(cookbook_name, path); end
# Called after all cookbooks have been sync'd.
def cookbook_sync_complete
@@ -166,16 +156,13 @@ class Chef
end
# Called before action is executed on a resource.
- def resource_action_start(resource, action, notification_type = nil, notifier = nil)
- end
+ 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
+ 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
+ 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)
@@ -183,8 +170,7 @@ class Chef
end
# Called after #load_current_resource has run.
- def resource_current_state_loaded(resource, action, current_resource)
- end
+ def resource_current_state_loaded(resource, action, current_resource); end
# Called when a resource has no converge actions, e.g., it was already correct.
def resource_up_to_date(resource, action)
@@ -209,24 +195,20 @@ class Chef
end
# Called before handlers run
- def handlers_start(handler_count)
- end
+ def handlers_start(handler_count); end
# Called after an individual handler has run
- def handler_executed(handler)
- end
+ def handler_executed(handler); end
# Called after all handlers have executed
- def handlers_completed
- end
+ def handlers_completed; end
# An uncategorized message. This supports the case that a user needs to
# pass output that doesn't fit into one of the callbacks above. Note that
# there's no semantic information about the content or importance of the
# message. That means that if you're using this too often, you should add a
# callback for it.
- def msg(message)
- end
+ def msg(message); end
end
end
diff --git a/lib/chef/guard_interpreter.rb b/lib/chef/guard_interpreter.rb
index e11201e50b..9deee91c1f 100644
--- a/lib/chef/guard_interpreter.rb
+++ b/lib/chef/guard_interpreter.rb
@@ -1,6 +1,6 @@
#
# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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/guard_interpreter/default_guard_interpreter"
-require "chef/guard_interpreter/resource_guard_interpreter"
+require_relative "guard_interpreter/default_guard_interpreter"
+require_relative "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 449ca9a316..7b4a22c9cc 100644
--- a/lib/chef/guard_interpreter/default_guard_interpreter.rb
+++ b/lib/chef/guard_interpreter/default_guard_interpreter.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Edwards (<adamed@chef.io>)
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,24 +16,27 @@
# limitations under the License.
#
-require "chef/mixin/shell_out"
+require_relative "../mixin/shell_out"
+require_relative "../exceptions"
class Chef
class GuardInterpreter
class DefaultGuardInterpreter
include Chef::Mixin::ShellOut
-
- protected
+ attr_reader :output
def initialize(command, opts)
@command = command
@command_opts = opts
end
- public
-
def evaluate
- shell_out(@command, @command_opts).status.success?
+ result = shell_out(@command, default_env: false, **@command_opts)
+ @output = "STDOUT: #{result.stdout}\nSTDERR: #{result.stderr}\n"
+ Chef::Log.debug "Command failed: #{result.stderr}" unless result.status.success?
+ result.status.success?
+ # Timeout fails command rather than chef-client run, see:
+ # https://tickets.opscode.com/browse/CHEF-2690
rescue Chef::Exceptions::CommandTimeout
Chef::Log.warn "Command '#{@command}' timed out"
false
diff --git a/lib/chef/guard_interpreter/resource_guard_interpreter.rb b/lib/chef/guard_interpreter/resource_guard_interpreter.rb
index 6df60aec89..a1e4205c03 100644
--- a/lib/chef/guard_interpreter/resource_guard_interpreter.rb
+++ b/lib/chef/guard_interpreter/resource_guard_interpreter.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Edwards (<adamed@chef.io>)
-# Copyright:: Copyright 2014-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,70 +16,72 @@
# limitations under the License.
#
-require "chef/guard_interpreter"
+require_relative "../guard_interpreter"
class Chef
class GuardInterpreter
- class ResourceGuardInterpreter < DefaultGuardInterpreter
-
+ class ResourceGuardInterpreter
def initialize(parent_resource, command, opts)
- super(command, opts)
+ @command = command
+ @opts = opts
+
@parent_resource = parent_resource
@resource = get_interpreter_resource(parent_resource)
end
+ # This class used to inherit from DefaultGuardInterpreter and it responds
+ # to #output, so leave this in for potential backwards compatibility.
+ def output
+ nil
+ end
+
def evaluate
# Add attributes inherited from the parent class
# to the resource
merge_inherited_attributes
+ @opts.each do |attribute, value|
+ @resource.send(attribute, value)
+ end
+
# Only execute and script resources and use guard attributes.
# The command to be executed on them are passed via different attributes.
# Script resources use code attribute and execute resources use
- # command attribute. Moreover script resources are also execute
+ # command property. Moreover script resources are also execute
# resources. Here we make sure @command is assigned to the right
# attribute by checking the type of the resources.
# We need to make sure we check for Script first because any resource
# that can get to here is an Execute resource.
if @resource.is_a? Chef::Resource::Script
- block_attributes = @command_opts.merge({ :code => @command })
+ @resource.code @command
else
- block_attributes = @command_opts.merge({ :command => @command })
+ @resource.command @command
end
# Handles cases like powershell_script where default
# attributes are different when used in a guard vs. not. For
# powershell_script in particular, this will go away when
- # the one attribue that causes this changes its default to be
+ # the one attribute that causes this changes its default to be
# the same after some period to prepare for deprecation
if @resource.class.respond_to?(:get_default_attributes)
- block_attributes = @resource.class.send(:get_default_attributes, @command_opts).merge(block_attributes)
+ @resource.class.send(:get_default_attributes).each do |attribute, value|
+ @resource.send(attribute, value)
+ end
end
- resource_block = block_from_attributes(block_attributes)
- evaluate_action(nil, &resource_block)
- end
-
- protected
-
- def evaluate_action(action = nil, &block)
- @resource.instance_eval(&block)
-
- run_action = action || @resource.action
-
begin
# 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
+ Array(@resource.action).each { |action_to_run| @resource.run_action(action_to_run) }
+ @resource.updated
rescue Mixlib::ShellOut::ShellCommandFailed
- resource_updated = nil
+ nil
end
-
- resource_updated
end
+ private
+
def get_interpreter_resource(parent_resource)
if parent_resource.nil? || parent_resource.node.nil?
raise ArgumentError, "Node for guard resource parent must not be nil"
@@ -91,7 +93,7 @@ class Chef
raise ArgumentError, "Specified guard_interpreter resource #{parent_resource.guard_interpreter} unknown for this platform"
end
- if ! resource_class.ancestors.include?(Chef::Resource::Execute)
+ unless 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
@@ -106,14 +108,6 @@ class Chef
interpreter_resource
end
- def block_from_attributes(attributes)
- Proc.new do
- attributes.keys.each do |attribute_name|
- send(attribute_name, attributes[attribute_name]) if respond_to?(attribute_name)
- end
- end
- end
-
def merge_inherited_attributes
inherited_attributes = []
@@ -121,15 +115,10 @@ class Chef
inherited_attributes = @parent_resource.class.send(:guard_inherited_attributes)
end
- if inherited_attributes && !inherited_attributes.empty?
- inherited_attributes.each do |attribute|
- if @parent_resource.respond_to?(attribute) && @resource.respond_to?(attribute)
- parent_value = @parent_resource.send(attribute)
- child_value = @resource.send(attribute)
- if parent_value || child_value
- @resource.send(attribute, parent_value)
- end
- end
+ inherited_attributes.each do |attribute|
+ if @parent_resource.respond_to?(attribute) && @resource.respond_to?(attribute)
+ parent_value = @parent_resource.send(attribute)
+ @resource.send(attribute, parent_value)
end
end
end
diff --git a/lib/chef/handler.rb b/lib/chef/handler.rb
index 100ed23d9e..97562ea31b 100644
--- a/lib/chef/handler.rb
+++ b/lib/chef/handler.rb
@@ -1,6 +1,6 @@
#--
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,18 +15,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-require "chef/client"
-require "forwardable"
+require_relative "client"
+require "forwardable" unless defined?(Forwardable)
class Chef
- # == Chef::Handler
# The base class for an Exception or Notification Handler. Create your own
# handler by subclassing Chef::Handler. When a Chef run fails with an
# uncaught Exception, Chef will set the +run_status+ on your handler and call
# +report+
#
- # ===Example:
- #
+ # @example
# require 'net/smtp'
#
# module MyOrg
@@ -236,13 +234,14 @@ class Chef
# The main entry point for report handling. Subclasses should override this
# method with their own report handling logic.
- def report
- end
+ def report; end
# Runs the report handler, rescuing and logging any errors it may cause.
# This ensures that all handlers get a chance to run even if one fails.
# This method should not be overridden by subclasses unless you know what
# you're doing.
+ #
+ # @api private
def run_report_safely(run_status)
run_report_unsafe(run_status)
rescue Exception => e
@@ -261,7 +260,7 @@ class Chef
# Return the Hash representation of the run_status
def data
- @run_status.to_hash
+ @run_status.to_h
end
end
diff --git a/lib/chef/handler/error_report.rb b/lib/chef/handler/error_report.rb
index e84a817e06..83d1d174db 100644
--- a/lib/chef/handler/error_report.rb
+++ b/lib/chef/handler/error_report.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/handler"
-require "chef/resource/directory"
+require_relative "../handler"
+require_relative "../resource/directory"
class Chef
class Handler
diff --git a/lib/chef/handler/json_file.rb b/lib/chef/handler/json_file.rb
index 9ba3d70383..6318c30d74 100644
--- a/lib/chef/handler/json_file.rb
+++ b/lib/chef/handler/json_file.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/handler"
-require "chef/resource/directory"
+require_relative "../handler"
+require_relative "../resource/directory"
class Chef
class Handler
@@ -28,7 +28,6 @@ class Chef
def initialize(config = {})
@config = config
@config[:path] ||= "/var/chef/reports"
- @config
end
def report
@@ -42,7 +41,7 @@ class Chef
savetime = Time.now.strftime("%Y%m%d%H%M%S")
File.open(File.join(config[:path], "chef-run-report-#{savetime}.json"), "w") do |file|
- #ensure start time and end time are output in the json properly in the event activesupport happens to be on the system
+ # ensure start time and end time are output in the json properly in the event activesupport happens to be on the system
run_data = data
run_data[:start_time] = run_data[:start_time].to_s
run_data[:end_time] = run_data[:end_time].to_s
diff --git a/lib/chef/http.rb b/lib/chef/http.rb
index 924081bc6b..162998b7f3 100644
--- a/lib/chef/http.rb
+++ b/lib/chef/http.rb
@@ -5,7 +5,7 @@
# Author:: Christopher Brown (<cb@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2009-2016, 2013-2015 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,14 +21,17 @@
# 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" unless defined?(Tempfile)
+autoload :OpenSSL, "openssl"
+autoload :URI, "uri"
+module Net
+ autoload :HTTP, "net/http"
+ autoload :HTTPClientException, "net/http"
+end
+require_relative "http/basic_client"
+require_relative "config"
+require_relative "platform/query_helpers"
+require_relative "exceptions"
class Chef
@@ -52,13 +55,12 @@ class Chef
def handle_chunk(next_chunk)
# stream handlers handle responses so must be applied in reverse order
- # (same as #apply_stream_complete_middleware or #apply_response_midddleware)
+ # (same as #apply_stream_complete_middleware or #apply_response_middleware)
@stream_handlers.reverse.inject(next_chunk) do |chunk, handler|
- Chef::Log.debug("Chef::HTTP::StreamHandler calling #{handler.class}#handle_chunk")
+ Chef::Log.trace("Chef::HTTP::StreamHandler calling #{handler.class}#handle_chunk")
handler.handle_chunk(chunk)
end
end
-
end
def self.middlewares
@@ -142,46 +144,62 @@ class Chef
# Makes an HTTP request to +path+ with the given +method+, +headers+, and
# +data+ (if applicable).
def request(method, path, headers = {}, data = false)
+ http_attempts ||= 0
url = create_url(path)
- method, url, headers, data = apply_request_middleware(method, url, headers, data)
+ processed_method, url, processed_headers, processed_data = apply_request_middleware(method, url, headers, data)
- response, rest_request, return_value = send_http_request(method, url, headers, data)
+ response, rest_request, return_value = send_http_request(processed_method, url, processed_headers, processed_data)
response, rest_request, return_value = apply_response_middleware(response, rest_request, return_value)
+
response.error! unless success_response?(response)
return_value
- rescue Exception => exception
- log_failed_request(response, return_value) unless response.nil?
- if exception.respond_to?(:chef_rest_request=)
- exception.chef_rest_request = rest_request
+ rescue Net::HTTPClientException => e
+ http_attempts += 1
+ response = e.response
+ if response.is_a?(Net::HTTPNotAcceptable) && version_retries - http_attempts >= 0
+ Chef::Log.trace("Negotiating protocol version with #{url}, retry #{http_attempts}/#{version_retries}")
+ retry
+ else
+ raise
end
+ rescue Exception => exception
+ log_failed_request(response, return_value) unless response.nil?
raise
end
- def streaming_request_with_progress(path, headers = {}, &progress_block)
+ def streaming_request_with_progress(path, headers = {}, tempfile = nil, &progress_block)
+ http_attempts ||= 0
url = create_url(path)
response, rest_request, return_value = nil, nil, nil
- tempfile = nil
+ data = nil
method = :GET
- method, url, headers, data = apply_request_middleware(method, url, headers, data)
+ method, url, processed_headers, data = apply_request_middleware(method, url, headers, data)
- response, rest_request, return_value = send_http_request(method, url, headers, data) do |http_response|
- if http_response.kind_of?(Net::HTTPSuccess)
- tempfile = stream_to_tempfile(url, http_response, &progress_block)
+ response, rest_request, return_value = send_http_request(method, url, processed_headers, data) do |http_response|
+ if http_response.is_a?(Net::HTTPSuccess)
+ tempfile = stream_to_tempfile(url, http_response, tempfile, &progress_block)
end
apply_stream_complete_middleware(http_response, rest_request, return_value)
end
- return nil if response.kind_of?(Net::HTTPRedirection)
- unless response.kind_of?(Net::HTTPSuccess)
+ return nil if response.is_a?(Net::HTTPRedirection)
+
+ unless response.is_a?(Net::HTTPSuccess)
response.error!
end
tempfile
+ rescue Net::HTTPClientException => e
+ http_attempts += 1
+ response = e.response
+ if response.is_a?(Net::HTTPNotAcceptable) && version_retries - http_attempts >= 0
+ Chef::Log.trace("Negotiating protocol version with #{url}, retry #{http_attempts}/#{version_retries}")
+ retry
+ else
+ raise
+ end
rescue Exception => e
log_failed_request(response, return_value) unless response.nil?
- if e.respond_to?(:chef_rest_request=)
- e.chef_rest_request = rest_request
- end
raise
end
@@ -193,24 +211,26 @@ class Chef
# you to unlink the tempfile when you're done with it.
#
# @yield [tempfile] block to process the tempfile
- # @yieldparams [tempfile<Tempfile>] tempfile
- def streaming_request(path, headers = {})
+ # @yieldparam [tempfile<Tempfile>] tempfile
+ def streaming_request(path, headers = {}, tempfile = nil)
+ http_attempts ||= 0
url = create_url(path)
response, rest_request, return_value = nil, nil, nil
- tempfile = nil
+ data = nil
method = :GET
- method, url, headers, data = apply_request_middleware(method, url, headers, data)
+ method, url, processed_headers, data = apply_request_middleware(method, url, headers, data)
- response, rest_request, return_value = send_http_request(method, url, headers, data) do |http_response|
- if http_response.kind_of?(Net::HTTPSuccess)
- tempfile = stream_to_tempfile(url, http_response)
+ response, rest_request, return_value = send_http_request(method, url, processed_headers, data) do |http_response|
+ if http_response.is_a?(Net::HTTPSuccess)
+ tempfile = stream_to_tempfile(url, http_response, tempfile)
end
apply_stream_complete_middleware(http_response, rest_request, return_value)
end
- return nil if response.kind_of?(Net::HTTPRedirection)
- unless response.kind_of?(Net::HTTPSuccess)
+ return nil if response.is_a?(Net::HTTPRedirection)
+
+ unless response.is_a?(Net::HTTPSuccess)
response.error!
end
@@ -222,11 +242,17 @@ class Chef
end
end
tempfile
+ rescue Net::HTTPClientException => e
+ http_attempts += 1
+ response = e.response
+ if response.is_a?(Net::HTTPNotAcceptable) && version_retries - http_attempts >= 0
+ Chef::Log.trace("Negotiating protocol version with #{url}, retry #{http_attempts}/#{version_retries}")
+ retry
+ else
+ raise
+ end
rescue Exception => e
log_failed_request(response, return_value) unless response.nil?
- if e.respond_to?(:chef_rest_request=)
- e.chef_rest_request = rest_request
- end
raise
end
@@ -235,7 +261,7 @@ class Chef
if keepalives && !base_url.nil?
# only reuse the http_client if we want keepalives and have a base_url
@http_client ||= {}
- # the per-host per-port cache here gets peristent connections correct when
+ # the per-host per-port cache here gets persistent connections correct when
# redirecting to different servers
if base_url.is_a?(String) # sigh, this kind of abuse can't happen with strongly typed languages
@http_client[base_url] ||= build_http_client(base_url)
@@ -258,6 +284,21 @@ class Chef
private
# @api private
+ def ssl_policy
+ return Chef::HTTP::APISSLPolicy unless @options[:ssl_verify_mode]
+
+ case @options[:ssl_verify_mode]
+ when :verify_none
+ Chef::HTTP::VerifyNoneSSLPolicy
+ when :verify_peer
+ Chef::HTTP::VerifyPeerSSLPolicy
+ else
+ Chef::Log.error("Chef::HTTP was passed an ssl_verify_mode of #{@options[:ssl_verify_mode]} which is unsupported. Falling back to the API policy")
+ Chef::HTTP::APISSLPolicy
+ end
+ end
+
+ # @api private
def build_http_client(base_url)
if chef_zero_uri?(base_url)
# PERFORMANCE CRITICAL: *MUST* lazy require here otherwise we load up webrick
@@ -265,19 +306,20 @@ class Chef
# when for most knife/chef-client work we never need/want this loaded.
unless defined?(SocketlessChefZeroClient)
- require "chef/http/socketless_chef_zero_client"
+ require_relative "http/socketless_chef_zero_client"
end
SocketlessChefZeroClient.new(base_url)
else
- BasicClient.new(base_url, ssl_policy: Chef::HTTP::APISSLPolicy, keepalives: keepalives)
+ BasicClient.new(base_url, ssl_policy: ssl_policy, keepalives: keepalives)
end
end
# @api private
def create_url(path)
return path if path.is_a?(URI)
- if path =~ /^(http|https|chefzero):\/\//i
+
+ if %r{^(http|https|chefzero)://}i.match?(path)
URI.parse(path)
elsif path.nil? || path.empty?
URI.parse(@url)
@@ -292,7 +334,7 @@ class Chef
# @api private
def apply_request_middleware(method, url, headers, data)
middlewares.inject([method, url, headers, data]) do |req_data, middleware|
- Chef::Log.debug("Chef::HTTP calling #{middleware.class}#handle_request")
+ Chef::Log.trace("Chef::HTTP calling #{middleware.class}#handle_request")
middleware.handle_request(*req_data)
end
end
@@ -300,7 +342,7 @@ class Chef
# @api private
def apply_response_middleware(response, rest_request, return_value)
middlewares.reverse.inject([response, rest_request, return_value]) do |res_data, middleware|
- Chef::Log.debug("Chef::HTTP calling #{middleware.class}#handle_response")
+ Chef::Log.trace("Chef::HTTP calling #{middleware.class}#handle_response")
middleware.handle_response(*res_data)
end
end
@@ -308,7 +350,7 @@ class Chef
# @api private
def apply_stream_complete_middleware(response, rest_request, return_value)
middlewares.reverse.inject([response, rest_request, return_value]) do |res_data, middleware|
- Chef::Log.debug("Chef::HTTP calling #{middleware.class}#handle_stream_complete")
+ Chef::Log.trace("Chef::HTTP calling #{middleware.class}#handle_stream_complete")
middleware.handle_stream_complete(*res_data)
end
end
@@ -323,34 +365,40 @@ class Chef
# @api private
def success_response?(response)
- response.kind_of?(Net::HTTPSuccess) || response.kind_of?(Net::HTTPRedirection)
+ response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPRedirection)
end
# Runs a synchronous HTTP request, with no middleware applied (use #request
# to have the middleware applied). The entire response will be loaded into memory.
# @api private
- def send_http_request(method, url, headers, body, &response_handler)
- headers = build_headers(method, url, headers, body)
-
+ def send_http_request(method, url, base_headers, body, &response_handler)
retrying_http_errors(url) do
+ headers = build_headers(method, url, base_headers, body)
client = http_client(url)
return_value = nil
if block_given?
request, response = client.request(method, url, body, headers, &response_handler)
else
- request, response = client.request(method, url, body, headers) { |r| r.read_body }
+ request, response = client.request(method, url, body, headers, &:read_body)
return_value = response.read_body
end
@last_response = response
- if response.kind_of?(Net::HTTPSuccess)
+ if response.is_a?(Net::HTTPSuccess)
[response, request, return_value]
- elsif response.kind_of?(Net::HTTPNotModified) # Must be tested before Net::HTTPRedirection because it's subclass.
+ elsif response.is_a?(Net::HTTPNotModified) # Must be tested before Net::HTTPRedirection because it's subclass.
[response, request, false]
elsif redirect_location = redirected_to(response)
- if [:GET, :HEAD].include?(method)
+ if %i{GET HEAD}.include?(method)
follow_redirect do
- send_http_request(method, url + redirect_location, headers, body, &response_handler)
+ redirected_url = url + redirect_location
+ if http_disable_auth_on_redirect
+ new_headers = build_headers(method, redirected_url, headers, body)
+ new_headers.delete("Authorization") if url.host != redirected_url.host
+ send_http_request(method, redirected_url, new_headers, body, &response_handler)
+ else
+ send_http_request(method, redirected_url, headers, body, &response_handler)
+ end
end
else
raise Exceptions::InvalidRedirect, "#{method} request was redirected from #{url} to #{redirect_location}. Only GET and HEAD support redirects."
@@ -372,8 +420,8 @@ class Chef
http_attempts += 1
response, request, return_value = yield
# handle HTTP 50X Error
- if response.kind_of?(Net::HTTPServerError) && !Chef::Config.local_mode
- if http_retry_count - http_attempts + 1 > 0
+ if response.is_a?(Net::HTTPServerError) && !Chef::Config.local_mode
+ if http_retry_count - http_attempts >= 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")
sleep(sleep_time)
@@ -383,7 +431,7 @@ class Chef
return [response, request, return_value]
end
rescue SocketError, Errno::ETIMEDOUT, Errno::ECONNRESET => e
- if http_retry_count - http_attempts + 1 > 0
+ if http_retry_count - http_attempts >= 0
Chef::Log.error("Error connecting to #{url}, retry #{http_attempts}/#{http_retry_count}")
sleep(http_retry_delay)
retry
@@ -391,21 +439,21 @@ class Chef
e.message.replace "Error connecting to #{url} - #{e.message}"
raise e
rescue Errno::ECONNREFUSED
- if http_retry_count - http_attempts + 1 > 0
+ if http_retry_count - http_attempts >= 0
Chef::Log.error("Connection refused connecting to #{url}, retry #{http_attempts}/#{http_retry_count}")
sleep(http_retry_delay)
retry
end
raise Errno::ECONNREFUSED, "Connection refused connecting to #{url}, giving up"
rescue Timeout::Error
- if http_retry_count - http_attempts + 1 > 0
+ if http_retry_count - http_attempts >= 0
Chef::Log.error("Timeout connecting to #{url}, retry #{http_attempts}/#{http_retry_count}")
sleep(http_retry_delay)
retry
end
raise Timeout::Error, "Timeout connecting to #{url}, giving up"
rescue OpenSSL::SSL::SSLError => e
- if (http_retry_count - http_attempts + 1 > 0) && !e.message.include?("certificate verify failed")
+ if (http_retry_count - http_attempts >= 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
@@ -414,6 +462,10 @@ class Chef
end
end
+ def version_retries
+ @version_retries ||= options[:version_class]&.possible_requests || 1
+ end
+
# @api private
def http_retry_delay
config[:http_retry_delay]
@@ -425,6 +477,11 @@ class Chef
end
# @api private
+ def http_disable_auth_on_redirect
+ config[:http_disable_auth_on_redirect]
+ end
+
+ # @api private
def config
Chef::Config
end
@@ -432,8 +489,9 @@ class Chef
# @api private
def follow_redirect
raise Chef::Exceptions::RedirectLimitExceeded if @redirects_followed >= redirect_limit
+
@redirects_followed += 1
- Chef::Log.debug("Following redirect #{@redirects_followed}/#{redirect_limit}")
+ Chef::Log.trace("Following redirect #{@redirects_followed}/#{redirect_limit}")
yield
ensure
@@ -448,9 +506,10 @@ class Chef
# @api private
def redirected_to(response)
- return nil unless response.kind_of?(Net::HTTPRedirection)
+ return nil unless response.is_a?(Net::HTTPRedirection)
# Net::HTTPNotModified is undesired subclass of Net::HTTPRedirection so test for this
- return nil if response.kind_of?(Net::HTTPNotModified)
+ return nil if response.is_a?(Net::HTTPNotModified)
+
response["location"]
end
@@ -463,13 +522,15 @@ class Chef
end
# @api private
- def stream_to_tempfile(url, response, &progress_block)
+ def stream_to_tempfile(url, response, tf = nil, &progress_block)
content_length = response["Content-Length"]
- tf = Tempfile.open("chef-rest")
- if Chef::Platform.windows?
- tf.binmode # required for binary files on Windows platforms
+ if tf.nil?
+ tf = Tempfile.open("chef-rest")
+ if ChefUtils.windows?
+ tf.binmode # required for binary files on Windows platforms
+ end
end
- Chef::Log.debug("Streaming download from #{url} to tempfile #{tf.path}")
+ Chef::Log.trace("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/api_versions.rb b/lib/chef/http/api_versions.rb
new file mode 100644
index 0000000000..701b8746b0
--- /dev/null
+++ b/lib/chef/http/api_versions.rb
@@ -0,0 +1,55 @@
+#--
+# Copyright:: Copyright (c) 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_relative "../server_api_versions"
+require_relative "../json_compat"
+
+class Chef
+ class HTTP
+ # An HTTP middleware to retrieve and store the Chef Server's minimum
+ # and maximum supported API versions.
+ class APIVersions
+
+ def initialize(options = {}); end
+
+ def handle_request(method, url, headers = {}, data = false)
+ [method, url, headers, data]
+ end
+
+ def handle_response(http_response, rest_request, return_value)
+ if http_response.code == "406"
+ ServerAPIVersions.instance.reset!
+ end
+ if http_response.key?("x-ops-server-api-version")
+ ServerAPIVersions.instance.set_versions(JSONCompat.parse(http_response["x-ops-server-api-version"]))
+ else
+ ServerAPIVersions.instance.unversioned!
+ end
+ [http_response, rest_request, return_value]
+ end
+
+ def stream_response_handler(response)
+ nil
+ end
+
+ def handle_stream_complete(http_response, rest_request, return_value)
+ [http_response, rest_request, return_value]
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/http/auth_credentials.rb b/lib/chef/http/auth_credentials.rb
index 053b2c938e..1702ae7e24 100644
--- a/lib/chef/http/auth_credentials.rb
+++ b/lib/chef/http/auth_credentials.rb
@@ -5,7 +5,7 @@
# Author:: Christopher Brown (<cb@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,16 +20,22 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-require "chef/log"
-require "mixlib/authentication/signedheaderauth"
+require_relative "../log"
+module Mixlib
+ module Authentication
+ autoload :SignedHeaderAuth, "mixlib/authentication/signedheaderauth"
+ end
+end
class Chef
class HTTP
class AuthCredentials
attr_reader :client_name, :key
- def initialize(client_name = nil, key = nil)
- @client_name, @key = client_name, key
+ def initialize(client_name = nil, key = nil, use_ssh_agent: false)
+ @client_name = client_name
+ @key = key
+ @use_ssh_agent = use_ssh_agent
end
def sign_requests?
@@ -38,7 +44,8 @@ class Chef
def signature_headers(request_params = {})
raise ArgumentError, "Cannot sign the request without a client name, check that :node_name is assigned" if client_name.nil?
- Chef::Log.debug("Signing the request as #{client_name}")
+
+ Chef::Log.trace("Signing the request as #{client_name}")
# params_in = {:http_method => :GET, :path => "/clients", :body => "", :host => "localhost"}
request_params = request_params.dup
@@ -48,8 +55,8 @@ class Chef
host = request_params.delete(:host) || "localhost"
sign_obj = Mixlib::Authentication::SignedHeaderAuth.signing_object(request_params)
- signed = sign_obj.sign(key).merge({ :host => host })
- signed.inject({}) { |memo, kv| memo["#{kv[0].to_s.upcase}"] = kv[1]; memo }
+ signed = sign_obj.sign(key, use_ssh_agent: @use_ssh_agent).merge({ host: host })
+ signed.inject({}) { |memo, kv| memo[(kv[0].to_s.upcase).to_s] = kv[1]; memo }
end
end
diff --git a/lib/chef/http/authenticator.rb b/lib/chef/http/authenticator.rb
index 84065bf816..80b32be750 100644
--- a/lib/chef/http/authenticator.rb
+++ b/lib/chef/http/authenticator.rb
@@ -1,6 +1,6 @@
#--
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,20 +16,22 @@
# limitations under the License.
#
-require "chef/http/auth_credentials"
-require "chef/exceptions"
-require "openssl"
+require_relative "auth_credentials"
+require_relative "../exceptions"
+autoload :OpenSSL, "openssl"
class Chef
class HTTP
class Authenticator
- DEFAULT_SERVER_API_VERSION = "1"
+ DEFAULT_SERVER_API_VERSION = "2".freeze
attr_reader :signing_key_filename
attr_reader :raw_key
attr_reader :attr_names
attr_reader :auth_credentials
+ attr_reader :version_class
+ attr_reader :api_version
attr_accessor :sign_request
@@ -38,16 +40,13 @@ class Chef
@sign_request = true
@signing_key_filename = opts[:signing_key_filename]
@key = load_signing_key(opts[:signing_key_filename], opts[:raw_key])
- @auth_credentials = AuthCredentials.new(opts[:client_name], @key)
- if opts[:api_version]
- @api_version = opts[:api_version]
- else
- @api_version = DEFAULT_SERVER_API_VERSION
- end
+ @auth_credentials = AuthCredentials.new(opts[:client_name], @key, use_ssh_agent: opts[:ssh_agent_signing])
+ @version_class = opts[:version_class]
+ @api_version = opts[:api_version]
end
def handle_request(method, url, headers = {}, data = false)
- headers["X-Ops-Server-API-Version"] = @api_version
+ headers["X-Ops-Server-API-Version"] = request_version
headers.merge!(authentication_headers(method, url, data, headers)) if sign_requests?
[method, url, headers, data]
end
@@ -64,6 +63,18 @@ class Chef
[http_response, rest_request, return_value]
end
+ def request_version
+ if version_class
+ version_class.best_request_version
+ elsif api_version
+ api_version
+ elsif Chef::ServerAPIVersions.instance.negotiated?
+ Chef::ServerAPIVersions.instance.max_server_version.to_s
+ else
+ DEFAULT_SERVER_API_VERSION
+ end
+ end
+
def sign_requests?
auth_credentials.sign_requests? && @sign_request
end
@@ -80,23 +91,26 @@ class Chef
else
return nil
end
- @key = OpenSSL::PKey::RSA.new(@raw_key)
+ # Pass in '' as the passphrase to avoid OpenSSL prompting on the TTY if
+ # given an encrypted key. This also helps if using a single file for
+ # both the public and private key with ssh-agent mode.
+ @key = OpenSSL::PKey::RSA.new(@raw_key, "")
rescue SystemCallError, IOError => e
Chef::Log.warn "Failed to read the private key #{key_file}: #{e.inspect}"
raise Chef::Exceptions::PrivateKeyMissing, "I cannot read #{key_file}, which you told me to use to sign requests!"
rescue OpenSSL::PKey::RSAError
- msg = "The file #{key_file} or :raw_key option does not contain a correctly formatted private key.\n"
+ msg = "The file #{key_file} or :raw_key option does not contain a correctly formatted private key or the key is encrypted.\n"
msg << "The key file should begin with '-----BEGIN RSA PRIVATE KEY-----' and end with '-----END RSA PRIVATE KEY-----'"
raise Chef::Exceptions::InvalidPrivateKey, msg
end
def authentication_headers(method, url, json_body = nil, headers = nil)
request_params = {
- :http_method => method,
- :path => url.path,
- :body => json_body,
- :host => "#{url.host}:#{url.port}",
- :headers => headers,
+ http_method: method,
+ path: url.path,
+ body: json_body,
+ host: "#{url.host}:#{url.port}",
+ headers: headers,
}
request_params[:body] ||= ""
auth_credentials.signature_headers(request_params)
diff --git a/lib/chef/http/basic_client.rb b/lib/chef/http/basic_client.rb
index 460744ea2a..2513b750eb 100644
--- a/lib/chef/http/basic_client.rb
+++ b/lib/chef/http/basic_client.rb
@@ -5,7 +5,7 @@
# Author:: Christopher Brown (<cb@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,10 +20,12 @@
# 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"
+autoload :URI, "uri"
+module Net
+ autoload :HTTP, "net/http"
+end
+require_relative "ssl_policies"
+require_relative "http_request"
class Chef
class HTTP
@@ -32,7 +34,6 @@ class Chef
HTTPS = "https".freeze
attr_reader :url
- attr_reader :http_client
attr_reader :ssl_policy
attr_reader :keepalives
@@ -61,32 +62,32 @@ class Chef
def request(method, url, req_body, base_headers = {})
http_request = HTTPRequest.new(method, url, req_body, base_headers).http_request
- Chef::Log.debug("Initiating #{method} to #{url}")
- Chef::Log.debug("---- HTTP Request Header Data: ----")
+ Chef::Log.trace("Initiating #{method} to #{url}")
+ Chef::Log.trace("---- HTTP Request Header Data: ----")
base_headers.each do |name, value|
- Chef::Log.debug("#{name}: #{value}")
+ Chef::Log.trace("#{name}: #{value}")
end
- Chef::Log.debug("---- End HTTP Request Header Data ----")
+ Chef::Log.trace("---- End HTTP Request Header Data ----")
http_client.request(http_request) do |response|
- Chef::Log.debug("---- HTTP Status and Header Data: ----")
- Chef::Log.debug("HTTP #{response.http_version} #{response.code} #{response.msg}")
+ Chef::Log.trace("---- HTTP Status and Header Data: ----")
+ Chef::Log.trace("HTTP #{response.http_version} #{response.code} #{response.msg}")
response.each do |header, value|
- Chef::Log.debug("#{header}: #{value}")
+ Chef::Log.trace("#{header}: #{value}")
end
- Chef::Log.debug("---- End HTTP Status/Header Data ----")
+ Chef::Log.trace("---- 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.body
- Chef::Log.debug("---- HTTP Response Body ----")
- Chef::Log.debug(response.body)
- Chef::Log.debug("---- End HTTP Response Body -----")
+ Chef::Log.trace("---- HTTP Response Body ----")
+ Chef::Log.trace(response.body)
+ Chef::Log.trace("---- End HTTP Response Body -----")
end
if req_body
- Chef::Log.debug("---- HTTP Request Body ----")
- Chef::Log.debug(req_body)
- Chef::Log.debug("---- End HTTP Request Body ----")
+ Chef::Log.trace("---- HTTP Request Body ----")
+ Chef::Log.trace(req_body)
+ Chef::Log.trace("---- End HTTP Request Body ----")
end
end
@@ -111,7 +112,7 @@ class Chef
# match no_proxy with a fuzzy matcher, rather than letting Net::HTTP
# do it.
http_client = http_client_builder.new(host, port, nil)
- http_client.proxy_port = nil if http_client.proxy_address == nil
+ http_client.proxy_port = nil if http_client.proxy_address.nil?
if url.scheme == HTTPS
configure_ssl(http_client)
@@ -134,9 +135,9 @@ class Chef
if proxy_uri.nil?
Net::HTTP
else
- Chef::Log.debug("Using #{proxy_uri.host}:#{proxy_uri.port} for proxy")
+ Chef::Log.trace("Using #{proxy_uri.host}:#{proxy_uri.port} for proxy")
Net::HTTP.Proxy(proxy_uri.host, proxy_uri.port, http_proxy_user(proxy_uri),
- http_proxy_pass(proxy_uri))
+ http_proxy_pass(proxy_uri))
end
end
diff --git a/lib/chef/http/cookie_jar.rb b/lib/chef/http/cookie_jar.rb
index 4908fde0c9..f0afbc6f52 100644
--- a/lib/chef/http/cookie_jar.rb
+++ b/lib/chef/http/cookie_jar.rb
@@ -5,7 +5,7 @@
# Author:: Christopher Brown (<cb@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,7 +20,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-require "singleton"
+require "singleton" unless defined?(Singleton)
class Chef
class HTTP
diff --git a/lib/chef/http/cookie_manager.rb b/lib/chef/http/cookie_manager.rb
index 18824a8eff..85b1a78e49 100644
--- a/lib/chef/http/cookie_manager.rb
+++ b/lib/chef/http/cookie_manager.rb
@@ -1,6 +1,6 @@
#--
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/http/cookie_jar"
+require_relative "cookie_jar"
class Chef
class HTTP
@@ -33,7 +33,7 @@ class Chef
def handle_request(method, url, headers = {}, data = false)
@host, @port = url.host, url.port
- if @cookies.has_key?("#{@host}:#{@port}")
+ if @cookies.key?("#{@host}:#{@port}")
headers["Cookie"] = @cookies["#{@host}:#{@port}"]
end
[method, url, headers, data]
diff --git a/lib/chef/http/decompressor.rb b/lib/chef/http/decompressor.rb
index 1bba4cc492..984c1deef1 100644
--- a/lib/chef/http/decompressor.rb
+++ b/lib/chef/http/decompressor.rb
@@ -1,6 +1,6 @@
#--
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,13 +16,13 @@
# limitations under the License.
#
-require "zlib"
-require "chef/http/http_request"
+require "zlib" unless defined?(Zlib)
+require_relative "http_request"
class Chef
class HTTP
- # Middleware-esque class for handling compression in HTTP responses.
+ # Middleware-ish class for handling compression in HTTP responses.
class Decompressor
class NoopInflater
def inflate(chunk)
@@ -64,6 +64,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
+
response_body = decompress_body(http_response)
http_response.body.replace(response_body) if http_response.body.respond_to?(:replace)
[http_response, rest_request, return_value]
@@ -79,10 +80,10 @@ class Chef
else
case response[CONTENT_ENCODING]
when GZIP
- Chef::Log.debug "Decompressing gzip response"
+ Chef::Log.trace "Decompressing gzip response"
Zlib::Inflate.new(Zlib::MAX_WBITS + 16).inflate(response.body)
when DEFLATE
- Chef::Log.debug "Decompressing deflate response"
+ Chef::Log.trace "Decompressing deflate response"
Zlib::Inflate.inflate(response.body)
else
response.body
@@ -94,20 +95,20 @@ class Chef
# object you can use to unzip/inflate a streaming response.
def stream_response_handler(response)
if gzip_disabled?
- Chef::Log.debug "disable_gzip is set. \
+ Chef::Log.trace "disable_gzip is set. \
Not using #{response[CONTENT_ENCODING]} \
and initializing noop stream deflator."
NoopInflater.new
else
case response[CONTENT_ENCODING]
when GZIP
- Chef::Log.debug "Initializing gzip stream deflator"
+ Chef::Log.trace "Initializing gzip stream deflator"
GzipInflater.new
when DEFLATE
- Chef::Log.debug "Initializing deflate stream deflator"
+ Chef::Log.trace "Initializing deflate stream deflator"
DeflateInflater.new
else
- Chef::Log.debug "content_encoding = '#{response[CONTENT_ENCODING]}' \
+ Chef::Log.trace "content_encoding = '#{response[CONTENT_ENCODING]}' \
initializing noop stream deflator."
NoopInflater.new
end
diff --git a/lib/chef/http/http_request.rb b/lib/chef/http/http_request.rb
index 20c46aaa8d..a188fe947e 100644
--- a/lib/chef/http/http_request.rb
+++ b/lib/chef/http/http_request.rb
@@ -5,7 +5,7 @@
# Author:: Christopher Brown (<cb@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2009-2016, 2010-2016 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,19 +20,23 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-require "uri"
-require "net/http"
+autoload :URI, "uri"
+autoload :CGI, "cgi"
+module Net
+ autoload :HTTP, "net/http"
+end
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
# 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" unless defined?(Ohai::System)
end
-require "chef/version"
+require_relative "../version"
class Chef
class HTTP
@@ -40,7 +44,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}; +https://chef.io)"
+ UA_COMMON = "/#{::Chef::VERSION} (#{engine}-#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}; ohai-#{Ohai::VERSION}; #{RUBY_PLATFORM}; +#{ChefUtils::Dist::Org::WEBSITE})".freeze
DEFAULT_UA = "Chef Client" << UA_COMMON
USER_AGENT = "User-Agent".freeze
@@ -49,6 +53,7 @@ class Chef
ENCODING_GZIP_DEFLATE = "gzip;q=1.0,deflate;q=0.6,identity;q=0.3".freeze
GET = "get".freeze
+ PATCH = "patch".freeze
PUT = "put".freeze
POST = "post".freeze
DELETE = "delete".freeze
@@ -70,7 +75,7 @@ class Chef
@user_agent ||= DEFAULT_UA
end
- attr_reader :method, :url, :headers, :http_client, :http_request
+ attr_reader :method, :url, :headers, :http_request
def initialize(method, url, req_body, base_headers = {})
@method, @url = method, url
@@ -99,7 +104,7 @@ class Chef
@url.path.empty? ? SLASH : @url.path
end
- # DEPRECATED. Call request on an HTTP client object instead.
+ # @deprecated Call request on an HTTP client object instead.
def call
hide_net_http_bug do
http_client.request(http_request) do |response|
@@ -113,7 +118,7 @@ class Chef
Chef::Config
end
- # DEPRECATED. Call request on an HTTP client object instead.
+ # @deprecated Call request on an HTTP client object instead.
def http_client
@http_client ||= BasicClient.new(url).http_client
end
@@ -125,10 +130,10 @@ 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})}/
- 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}")
- Chef::Log.debug(e.backtrace.join("\n"))
+ if /#{Regexp.escape(%q{undefined method `closed?' for nil:NilClass})}/.match?(e.to_s)
+ Chef::Log.trace("Rescued error in http connect, re-raising as Errno::ECONNREFUSED to hide bug in net/http")
+ Chef::Log.trace("#{e.class.name}: #{e}")
+ Chef::Log.trace(e.backtrace.join("\n"))
raise Errno::ECONNREFUSED, "Connection refused attempting to contact #{url.scheme}://#{host}:#{port}"
else
raise
@@ -151,7 +156,7 @@ class Chef
end
def configure_http_request(request_body = nil)
- req_path = "#{path}"
+ req_path = path.to_s.dup
req_path << "?#{query}" if query
@http_request = case method.to_s.downcase
@@ -161,6 +166,8 @@ class Chef
Net::HTTP::Post.new(req_path, headers)
when PUT
Net::HTTP::Put.new(req_path, headers)
+ when PATCH
+ Net::HTTP::Patch.new(req_path, headers)
when DELETE
Net::HTTP::Delete.new(req_path, headers)
when HEAD
@@ -172,8 +179,8 @@ class Chef
@http_request.body = request_body if request_body && @http_request.request_body_permitted?
# Optionally handle HTTP Basic Authentication
if url.user
- user = URI.unescape(url.user)
- password = URI.unescape(url.password) if url.password
+ user = CGI.unescape(url.user)
+ password = CGI.unescape(url.password) if url.password
@http_request.basic_auth(user, password)
end
diff --git a/lib/chef/http/json_input.rb b/lib/chef/http/json_input.rb
index 4cc1aa2e10..1d96743770 100644
--- a/lib/chef/http/json_input.rb
+++ b/lib/chef/http/json_input.rb
@@ -1,7 +1,7 @@
#--
# Author:: Daniel DeLeo (<dan@chef.io>)
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/json_compat"
+require_relative "../json_compat"
class Chef
class HTTP
@@ -33,10 +33,10 @@ class Chef
def handle_request(method, url, headers = {}, data = false)
if data && should_encode_as_json?(headers)
- headers.delete_if { |key, _value| key.casecmp("content-type").zero? }
+ headers.delete_if { |key, _value| key.casecmp("content-type") == 0 }
headers["Content-Type"] = "application/json"
json_opts = {}
- json_opts[:validate_utf8] = opts[:validate_utf8] if opts.has_key?(:validate_utf8)
+ json_opts[:validate_utf8] = opts[:validate_utf8] if opts.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
@@ -64,7 +64,7 @@ class Chef
# ruby/Net::HTTP don't enforce capitalized headers (it normalizes them
# for you before sending the request), so we have to account for all
# the variations we might find
- requested_content_type = headers.find { |k, v| k.casecmp("content-type").zero? }
+ requested_content_type = headers.find { |k, v| k.casecmp("content-type") == 0 }
requested_content_type.nil? || requested_content_type.last.include?("json")
end
diff --git a/lib/chef/http/json_output.rb b/lib/chef/http/json_output.rb
index 6053c38a56..bad009bd14 100644
--- a/lib/chef/http/json_output.rb
+++ b/lib/chef/http/json_output.rb
@@ -1,7 +1,7 @@
#--
# Author:: Daniel DeLeo (<dan@chef.io>)
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/json_compat"
-require "chef/log"
+require_relative "../json_compat"
+require_relative "../log"
class Chef
class HTTP
@@ -46,7 +46,8 @@ 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 /json/.match?(http_response["content-type"])
if http_response.body.nil?
return_value = nil
elsif raw_output
@@ -60,8 +61,11 @@ class Chef
end
[http_response, rest_request, return_value]
else
- Chef::Log.debug("Expected JSON response, but got content-type '#{http_response['content-type']}'")
- return [http_response, rest_request, http_response.body.to_s]
+ Chef::Log.trace("Expected JSON response, but got content-type '#{http_response["content-type"]}'")
+ if http_response.body
+ Chef::Log.trace("Response body contains:\n#{http_response.body.length < 256 ? http_response.body : http_response.body[0..256] + " [...truncated...]"}")
+ end
+ [http_response, rest_request, http_response.body.to_s]
end
end
diff --git a/lib/chef/http/json_to_model_output.rb b/lib/chef/http/json_to_model_output.rb
index 12ca1a90aa..20ebbdea29 100644
--- a/lib/chef/http/json_to_model_output.rb
+++ b/lib/chef/http/json_to_model_output.rb
@@ -1,6 +1,6 @@
#--
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/http/json_output"
+require_relative "json_output"
class Chef
class HTTP
@@ -26,7 +26,7 @@ class Chef
# a `json_class` key.
class JSONToModelOutput < JSONOutput
def initialize(opts = {})
- opts[:inflate_json_class] = true if !opts.has_key?(:inflate_json_class)
+ opts[:inflate_json_class] = true unless opts.key?(:inflate_json_class)
super
end
end
diff --git a/lib/chef/http/remote_request_id.rb b/lib/chef/http/remote_request_id.rb
index a779df805e..7b0bd0f830 100644
--- a/lib/chef/http/remote_request_id.rb
+++ b/lib/chef/http/remote_request_id.rb
@@ -1,5 +1,5 @@
# Author:: Prajakta Purohit (<prajakta@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,14 +15,13 @@
# limitations under the License.
#
-require "chef/request_id"
+require_relative "../request_id"
class Chef
class HTTP
class RemoteRequestID
- def initialize(opts = {})
- end
+ def initialize(opts = {}); end
def handle_request(method, url, headers = {}, data = false)
headers["X-REMOTE-REQUEST-ID"] = Chef::RequestID.instance.request_id
diff --git a/lib/chef/http/simple.rb b/lib/chef/http/simple.rb
index bdbc31c9f1..84bc621e74 100644
--- a/lib/chef/http/simple.rb
+++ b/lib/chef/http/simple.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,11 +16,11 @@
# 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"
+require_relative "../http"
+require_relative "authenticator"
+require_relative "decompressor"
+require_relative "cookie_manager"
+require_relative "validate_content_length"
class Chef
class HTTP
diff --git a/lib/chef/http/simple_json.rb b/lib/chef/http/simple_json.rb
index 7357d859ee..0e1aad99ac 100644
--- a/lib/chef/http/simple_json.rb
+++ b/lib/chef/http/simple_json.rb
@@ -1,6 +1,6 @@
#
# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,11 +16,11 @@
# 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"
+require_relative "../http"
+require_relative "authenticator"
+require_relative "decompressor"
+require_relative "cookie_manager"
+require_relative "validate_content_length"
class Chef
class HTTP
diff --git a/lib/chef/http/socketless_chef_zero_client.rb b/lib/chef/http/socketless_chef_zero_client.rb
index d2f1f45b1d..8c7ee8d8bc 100644
--- a/lib/chef/http/socketless_chef_zero_client.rb
+++ b/lib/chef/http/socketless_chef_zero_client.rb
@@ -1,6 +1,6 @@
#--
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -44,6 +44,10 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
require "chef_zero/server"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+module Net
+ autoload :HTTPResponse, "net/http"
+end
class Chef
class HTTP
@@ -63,7 +67,7 @@ class Chef
# 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"
+ raise "responses from socketless #{ChefUtils::Dist::Zero::PRODUCT} can't be written to specific destination"
end
if block_given?
@@ -131,9 +135,9 @@ class Chef
505 => "HTTP Version Not Supported",
507 => "Insufficient Storage",
511 => "Network Authentication Required",
- }
+ }.freeze
- STATUS_MESSAGE.values.each { |v| v.freeze }
+ STATUS_MESSAGE.each_value(&:freeze)
STATUS_MESSAGE.freeze
def initialize(base_url)
@@ -163,15 +167,16 @@ class Chef
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}",
+ "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}",
+ "HTTP_X_OPS_SERVER_API_VERSION" => headers["X-Ops-Server-API-Version"],
"rack.url_scheme" => "chefzero",
- "rack.input" => StringIO.new(body_str),
+ "rack.input" => StringIO.new(body_str),
}
end
@@ -179,6 +184,7 @@ class Chef
body = chunked_body.join("")
msg = STATUS_MESSAGE[code]
raise "Cannot determine HTTP status message for code #{code}" unless msg
+
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|
@@ -198,7 +204,7 @@ class Chef
def headers_extracted_from_options
options.reject { |name, _| KNOWN_OPTIONS.include?(name) }.map do |name, value|
- [name.to_s.split("_").map { |segment| segment.capitalize }.join("-"), value]
+ [name.to_s.split("_").map(&:capitalize).join("-"), value]
end
end
diff --git a/lib/chef/http/ssl_policies.rb b/lib/chef/http/ssl_policies.rb
index a3692b81b6..bc688f13a6 100644
--- a/lib/chef/http/ssl_policies.rb
+++ b/lib/chef/http/ssl_policies.rb
@@ -5,7 +5,7 @@
# Author:: Christopher Brown (<cb@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,8 +21,8 @@
# limitations under the License.
#
-require "openssl"
-require "chef/util/path_helper"
+autoload :OpenSSL, "openssl"
+require_relative "../util/path_helper"
class Chef
class HTTP
@@ -62,12 +62,20 @@ class Chef
unless ::File.exist?(config[:ssl_ca_path])
raise Chef::Exceptions::ConfigurationError, "The configured ssl_ca_path #{config[:ssl_ca_path]} does not exist"
end
+
http_client.ca_path = config[:ssl_ca_path]
elsif config[:ssl_ca_file]
unless ::File.exist?(config[:ssl_ca_file])
raise Chef::Exceptions::ConfigurationError, "The configured ssl_ca_file #{config[:ssl_ca_file]} does not exist"
end
+
http_client.ca_file = config[:ssl_ca_file]
+ elsif ENV["SSL_CERT_FILE"]
+ unless ::File.exist?(ENV["SSL_CERT_FILE"])
+ raise Chef::Exceptions::ConfigurationError, "The configured ssl_ca_file #{ENV["SSL_CERT_FILE"]} does not exist"
+ end
+
+ http_client.ca_file = ENV["SSL_CERT_FILE"]
end
end
@@ -77,27 +85,41 @@ class Chef
http_client.cert_store.set_default_paths
end
if config.trusted_certs_dir
- certs = Dir.glob(File.join(Chef::Util::PathHelper.escape_glob_dir(config.trusted_certs_dir), "*.{crt,pem}"))
+ certs = Dir.glob(::File.join(Chef::Util::PathHelper.escape_glob_dir(config.trusted_certs_dir), "*.{crt,pem}"))
certs.each do |cert_file|
- cert = OpenSSL::X509::Certificate.new(File.read(cert_file))
+ cert = begin
+ OpenSSL::X509::Certificate.new(::File.binread(cert_file))
+ rescue OpenSSL::X509::CertificateError => e
+ raise Chef::Exceptions::ConfigurationError, "Error reading cert file '#{cert_file}', original error '#{e.class}: #{e.message}'"
+ end
add_trusted_cert(cert)
end
end
end
def set_client_credentials
- if config[:ssl_client_cert] || config[:ssl_client_key]
- unless config[:ssl_client_cert] && config[:ssl_client_key]
- raise Chef::Exceptions::ConfigurationError, "You must configure ssl_client_cert and ssl_client_key together"
- end
- unless ::File.exists?(config[:ssl_client_cert])
- raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_cert #{config[:ssl_client_cert]} does not exist"
- end
- unless ::File.exists?(config[:ssl_client_key])
- raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_key #{config[:ssl_client_key]} does not exist"
- end
- http_client.cert = OpenSSL::X509::Certificate.new(::File.read(config[:ssl_client_cert]))
- http_client.key = OpenSSL::PKey::RSA.new(::File.read(config[:ssl_client_key]))
+ return unless config[:ssl_client_cert] || config[:ssl_client_key]
+
+ unless config[:ssl_client_cert] && config[:ssl_client_key]
+ raise Chef::Exceptions::ConfigurationError, "You must configure ssl_client_cert and ssl_client_key together"
+ end
+ unless ::File.exists?(config[:ssl_client_cert])
+ raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_cert #{config[:ssl_client_cert]} does not exist"
+ end
+ unless ::File.exists?(config[:ssl_client_key])
+ raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_key #{config[:ssl_client_key]} does not exist"
+ end
+
+ begin
+ http_client.cert = OpenSSL::X509::Certificate.new(::File.binread(config[:ssl_client_cert]))
+ rescue OpenSSL::X509::CertificateError => e
+ raise Chef::Exceptions::ConfigurationError, "Error reading cert file '#{config[:ssl_client_cert]}', original error '#{e.class}: #{e.message}'"
+ end
+
+ begin
+ http_client.key = OpenSSL::PKey::RSA.new(::File.binread(config[:ssl_client_key]))
+ rescue OpenSSL::PKey::RSAError => e
+ raise Chef::Exceptions::ConfigurationError, "Error reading key file '#{config[:ssl_client_key]}', original error '#{e.class}: #{e.message}'"
end
end
@@ -126,5 +148,23 @@ class Chef
end
end
+ # This policy is used when we want to explicitly turn on verification
+ # for a specific request regardless of the API Policy. For example, when
+ # doing a `remote_file` where the user specified `verify_mode :verify_peer`
+ class VerifyPeerSSLPolicy < DefaultSSLPolicy
+ def set_verify_mode
+ http_client.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ end
+ end
+
+ # This policy is used when we want to explicitly turn off verification
+ # for a specific request regardless of the API Policy. For example, when
+ # doing a `remote_file` where the user specified `verify_mode :verify_none`
+ class VerifyNoneSSLPolicy < DefaultSSLPolicy
+ def set_verify_mode
+ http_client.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ end
+ end
+
end
end
diff --git a/lib/chef/http/validate_content_length.rb b/lib/chef/http/validate_content_length.rb
index c1073867d3..fb20227e31 100644
--- a/lib/chef/http/validate_content_length.rb
+++ b/lib/chef/http/validate_content_length.rb
@@ -1,6 +1,6 @@
#--
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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 "pp"
-require "chef/log"
+require "pp" unless defined?(PP)
+require_relative "../log"
class Chef
class HTTP
@@ -41,8 +41,7 @@ class Chef
end
end
- def initialize(opts = {})
- end
+ def initialize(opts = {}); end
def handle_request(method, url, headers = {}, data = false)
[method, url, headers, data]
@@ -50,12 +49,12 @@ class Chef
def handle_response(http_response, rest_request, return_value)
validate(http_response, http_response.body.bytesize) if http_response && http_response.body
- return [http_response, rest_request, return_value]
+ [http_response, rest_request, return_value]
end
def handle_stream_complete(http_response, rest_request, return_value)
if @content_length_counter.nil?
- Chef::Log.debug("No content-length information collected for the streamed download, cannot identify streamed download.")
+ Chef::Log.trace("No content-length information collected for the streamed download, cannot identify streamed download.")
else
validate(http_response, @content_length_counter.content_length)
end
@@ -63,7 +62,7 @@ class Chef
# Make sure the counter is reset since this object might get used
# again. See CHEF-5100
@content_length_counter = nil
- return [http_response, rest_request, return_value]
+ [http_response, rest_request, return_value]
end
def stream_response_handler(response)
@@ -74,6 +73,7 @@ class Chef
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
else
@@ -86,19 +86,19 @@ class Chef
transfer_encoding = http_response["transfer-encoding"]
if content_length.nil?
- Chef::Log.debug "HTTP server did not include a Content-Length header in response, cannot identify truncated downloads."
+ Chef::Log.trace "HTTP server did not include a Content-Length header in response, cannot identify truncated downloads."
return true
end
if content_length < 0
- Chef::Log.debug "HTTP server responded with a negative Content-Length header (#{content_length}), cannot identify truncated downloads."
+ Chef::Log.trace "HTTP server responded with a negative Content-Length header (#{content_length}), cannot identify truncated downloads."
return true
end
# if Transfer-Encoding is set the RFC states that we must ignore the Content-Length field
# CHEF-5041: some proxies uncompress gzip content, leave the incorrect content-length, but set the transfer-encoding field
unless transfer_encoding.nil?
- Chef::Log.debug "Transfer-Encoding header is set, skipping Content-Length check."
+ Chef::Log.trace "Transfer-Encoding header is set, skipping Content-Length check."
return true
end
@@ -106,7 +106,7 @@ class Chef
raise Chef::Exceptions::ContentLengthMismatch.new(response_length, content_length)
end
- Chef::Log.debug "Content-Length validated correctly."
+ Chef::Log.trace "Content-Length validated correctly."
true
end
end
diff --git a/lib/chef/json_compat.rb b/lib/chef/json_compat.rb
index f8f05a0074..b5c9072f6f 100644
--- a/lib/chef/json_compat.rb
+++ b/lib/chef/json_compat.rb
@@ -1,6 +1,6 @@
#
# Author:: Tim Hinderliter (<tim@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,148 +17,47 @@
# Wrapper class for interacting with JSON.
-require "ffi_yajl"
-require "chef/exceptions"
+autoload :FFI_Yajl, "ffi_yajl"
+require_relative "exceptions"
# We're requiring this to prevent breaking consumers using Hash.to_json
-require "json"
+require "json" unless defined?(JSON)
class Chef
class JSONCompat
- JSON_MAX_NESTING = 1000
-
- JSON_CLASS = "json_class".freeze
-
- CHEF_APICLIENT = "Chef::ApiClient".freeze
- CHEF_CHECKSUM = "Chef::Checksum".freeze
- CHEF_COOKBOOKVERSION = "Chef::CookbookVersion".freeze
- CHEF_DATABAG = "Chef::DataBag".freeze
- CHEF_DATABAGITEM = "Chef::DataBagItem".freeze
- CHEF_ENVIRONMENT = "Chef::Environment".freeze
- CHEF_NODE = "Chef::Node".freeze
- CHEF_ROLE = "Chef::Role".freeze
- CHEF_SANDBOX = "Chef::Sandbox".freeze
- CHEF_RESOURCE = "Chef::Resource".freeze
- CHEF_RESOURCECOLLECTION = "Chef::ResourceCollection".freeze
- CHEF_RESOURCESET = "Chef::ResourceCollection::ResourceSet".freeze
- CHEF_RESOURCELIST = "Chef::ResourceCollection::ResourceList".freeze
- CHEF_RUNLISTEXPANSION = "Chef::RunListExpansion".freeze
class <<self
- # API to use to avoid create_addtions
def parse(source, opts = {})
- begin
- FFI_Yajl::Parser.parse(source, opts)
- rescue FFI_Yajl::ParseError => e
- raise Chef::Exceptions::JSON::ParseError, e.message
- end
+ FFI_Yajl::Parser.parse(source, opts)
+ rescue FFI_Yajl::ParseError => e
+ raise Chef::Exceptions::JSON::ParseError, e.message
end
- # Just call the JSON gem's parse method with a modified :max_nesting field
def from_json(source, opts = {})
obj = parse(source, opts)
# JSON gem requires top level object to be a Hash or Array (otherwise
# you get the "must contain two octets" error). Yajl doesn't impose the
# same limitation. For compatibility, we re-impose this condition.
- unless obj.kind_of?(Hash) || obj.kind_of?(Array)
+ unless obj.is_a?(Hash) || obj.is_a?(Array)
raise Chef::Exceptions::JSON::ParseError, "Top level JSON object must be a Hash or Array. (actual: #{obj.class})"
end
- # The old default in the json gem (which we are mimicing because we
- # sadly rely on this misfeature) is to "create additions" i.e., convert
- # JSON objects into ruby objects. Explicit :create_additions => false
- # is required to turn it off.
- if opts[:create_additions].nil? || opts[:create_additions]
- map_to_rb_obj(obj)
- else
- obj
- end
- end
-
- # Look at an object that's a basic type (from json parse) and convert it
- # to an instance of Chef classes if desired.
- def map_to_rb_obj(json_obj)
- case json_obj
- when Hash
- mapped_hash = map_hash_to_rb_obj(json_obj)
- if json_obj.has_key?(JSON_CLASS) && (class_to_inflate = class_for_json_class(json_obj[JSON_CLASS]))
- class_to_inflate.json_create(mapped_hash)
- else
- mapped_hash
- end
- when Array
- json_obj.map { |e| map_to_rb_obj(e) }
- else
- json_obj
- end
- end
-
- def map_hash_to_rb_obj(json_hash)
- json_hash.each do |key, value|
- json_hash[key] = map_to_rb_obj(value)
- end
- json_hash
+ obj
end
def to_json(obj, opts = nil)
- begin
- FFI_Yajl::Encoder.encode(obj, opts)
- rescue FFI_Yajl::EncodeError => e
- raise Chef::Exceptions::JSON::EncodeError, e.message
- end
+ FFI_Yajl::Encoder.encode(obj, opts)
+ rescue FFI_Yajl::EncodeError => e
+ raise Chef::Exceptions::JSON::EncodeError, e.message
end
def to_json_pretty(obj, opts = nil)
- opts ||= {}
- options_map = {}
- options_map[:pretty] = true
- options_map[:indent] = opts[:indent] if opts.has_key?(:indent)
+ options_map = { pretty: true }
+ options_map[:indent] = opts[:indent] if opts.respond_to?(:key?) && opts.key?(:indent)
to_json(obj, options_map).chomp
end
- # Map +json_class+ to a Class object. We use a +case+ instead of a Hash
- # assigned to a constant because otherwise this file could not be loaded
- # until all the constants were defined, which means you'd have to load
- # the world to get json, which would make knife very slow.
- def class_for_json_class(json_class)
- case json_class
- when CHEF_APICLIENT
- Chef::ApiClient
- when CHEF_CHECKSUM
- Chef::Checksum
- when CHEF_COOKBOOKVERSION
- Chef::CookbookVersion
- when CHEF_DATABAG
- Chef::DataBag
- when CHEF_DATABAGITEM
- Chef::DataBagItem
- when CHEF_ENVIRONMENT
- Chef::Environment
- when CHEF_NODE
- Chef::Node
- when CHEF_ROLE
- Chef::Role
- when CHEF_SANDBOX
- # a falsey return here will disable object inflation/"create
- # additions" in the caller. In Chef 11 this is correct, we just have
- # a dummy Chef::Sandbox class for compat with Chef 10 servers.
- false
- when CHEF_RESOURCE
- Chef::Resource
- when CHEF_RESOURCECOLLECTION
- Chef::ResourceCollection
- when CHEF_RESOURCESET
- Chef::ResourceCollection::ResourceSet
- when CHEF_RESOURCELIST
- Chef::ResourceCollection::ResourceList
- when /^Chef::Resource/
- Chef::Resource.find_descendants_by_name(json_class)
- else
- raise Chef::Exceptions::JSON::ParseError, "Unsupported `json_class` type '#{json_class}'"
- end
- end
-
end
end
end
diff --git a/lib/chef/key.rb b/lib/chef/key.rb
index 38822e8b26..f1c046e6ba 100644
--- a/lib/chef/key.rb
+++ b/lib/chef/key.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Cloke (tyler@chef.io)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,10 +16,10 @@
# limitations under the License.
#
-require "chef/json_compat"
-require "chef/mixin/params_validate"
-require "chef/exceptions"
-require "chef/server_api"
+require_relative "json_compat"
+require_relative "mixin/params_validate"
+require_relative "exceptions"
+require_relative "server_api"
class Chef
# Class for interacting with a chef key object. Can be used to create new keys,
@@ -46,7 +46,7 @@ class Chef
# Actor that the key is for, either a client or a user.
@actor = actor
- unless actor_field_name == "user" || actor_field_name == "client"
+ unless %w{user client}.include?(actor_field_name)
raise Chef::Exceptions::InvalidKeyArgument, "the second argument to initialize must be either 'user' or 'client'"
end
@@ -77,23 +77,24 @@ class Chef
def actor(arg = nil)
set_or_return(:actor, arg,
- :regex => /^[a-z0-9\-_]+$/)
+ regex: /^[a-z0-9\-_]+$/)
end
def name(arg = nil)
set_or_return(:name, arg,
- :kind_of => String)
+ 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)
+ kind_of: String)
end
def private_key(arg = nil)
set_or_return(:private_key, arg,
- :kind_of => String)
+ kind_of: String)
end
def delete_public_key
@@ -106,16 +107,17 @@ class Chef
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])
+ 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)$/)
+ regex: /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z|infinity)$/)
end
- def to_hash
+ def to_h
result = {
@actor_field_name => @actor,
}
@@ -127,8 +129,10 @@ class Chef
result
end
+ alias_method :to_hash, :to_h
+
def to_json(*a)
- Chef::JSONCompat.to_json(to_hash, *a)
+ Chef::JSONCompat.to_json(to_h, *a)
end
def create
@@ -140,7 +144,7 @@ class Chef
# 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
+ # then they must supply a name because we can't generate a fingerprint
unless @public_key.nil?
@name = fingerprint
else
@@ -155,7 +159,7 @@ class Chef
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 = to_h
new_key["private_key"] = result["private_key"] if result["private_key"]
Chef::Key.from_hash(new_key)
end
@@ -175,17 +179,17 @@ class Chef
# to @name.
put_name = @name if put_name.nil?
- new_key = chef_rest.put("#{api_base}/#{@actor}/keys/#{put_name}", to_hash)
+ new_key = chef_rest.put("#{api_base}/#{@actor}/keys/#{put_name}", to_h)
# 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
+ delete_create_key
end
- Chef::Key.from_hash(self.to_hash.merge(new_key))
+ Chef::Key.from_hash(to_h.merge(new_key))
end
def save
create
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => e
if e.response.code == "409"
update
else
@@ -203,9 +207,9 @@ class Chef
class << self
def from_hash(key_hash)
- if key_hash.has_key?("user")
+ if key_hash.key?("user")
key = Chef::Key.new(key_hash["user"], "user")
- elsif key_hash.has_key?("client")
+ elsif key_hash.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."
@@ -222,19 +226,14 @@ class Chef
Chef::Key.from_hash(Chef::JSONCompat.from_json(json))
end
- def json_create(json)
- Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::Key#from_json or one of the load_by methods.")
- Chef::Key.from_json(json)
- end
-
def 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)
+ list(keys, actor, :load_by_user, inflate)
end
def 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)
+ list(keys, actor, :load_by_client, inflate)
end
def load_by_user(actor, key_name)
@@ -253,7 +252,7 @@ class Chef
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(":")
+ OpenSSL::Digest.hexdigest("SHA1", data_string.to_der).scan(/../).join(":")
end
def list(keys, actor, load_method_symbol, inflate)
diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb
index c9ecfbf0cc..ac7a68d0fc 100644
--- a/lib/chef/knife.rb
+++ b/lib/chef/knife.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Brown (<cb@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,34 +17,36 @@
# 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/server_api"
-require "chef/http/authenticator"
-require "chef/http/http_request"
-require "chef/http"
-require "pp"
+require "forwardable" unless defined?(Forwardable)
+require_relative "version"
+require "mixlib/cli" unless defined?(Mixlib::CLI)
+require "chef-utils/dsl/default_paths" unless defined?(ChefUtils::DSL::DefaultPaths)
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+require_relative "workstation_config_loader"
+require_relative "mixin/convert_to_class_name"
+require_relative "mixin/default_paths"
+require_relative "knife/core/subcommand_loader"
+require_relative "knife/core/ui"
+require_relative "local_mode"
+require_relative "server_api"
+require_relative "http/authenticator"
+require_relative "http/http_request"
+require_relative "http"
+require "pp" unless defined?(PP)
class Chef
class Knife
- Chef::HTTP::HTTPRequest.user_agent = "Chef Knife#{Chef::HTTP::HTTPRequest::UA_COMMON}"
+ Chef::HTTP::HTTPRequest.user_agent = "#{ChefUtils::Dist::Infra::PRODUCT} Knife#{Chef::HTTP::HTTPRequest::UA_COMMON}"
include Mixlib::CLI
- include Chef::Mixin::PathSanity
+ include ChefUtils::DSL::DefaultPaths
extend Chef::Mixin::ConvertToClassName
extend Forwardable
- # Backwards Compat:
- # Ideally, we should not vomit all of these methods into this base class;
- # instead, they should be accessed by hitting the ui object directly.
+ # @note Backwards Compat:
+ # Ideally, we should not vomit all of these methods into this base class;
+ # instead, they should be accessed by hitting the ui object directly.
def_delegator :@ui, :stdout
def_delegator :@ui, :stderr
def_delegator :@ui, :stdin
@@ -63,6 +65,12 @@ class Chef
attr_accessor :name_args
attr_accessor :ui
+ # knife acl subcommands are grouped in this category using this constant to verify.
+ OPSCODE_HOSTED_CHEF_ACCESS_CONTROL = %w{acl group user}.freeze
+
+ # knife opc subcommands are grouped in this category using this constant to verify.
+ CHEF_ORGANIZATION_MANAGEMENT = %w{opc}.freeze
+
# Configure mixlib-cli to always separate defaults from user-supplied CLI options
def self.use_separate_defaults?
true
@@ -87,12 +95,13 @@ class Chef
end
def self.inherited(subclass)
+ super
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
+ # inherited 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])]
@@ -105,12 +114,11 @@ class Chef
# Explicitly set the category for the current command to +new_category+
# The category is normally determined from the first word of the command
# name, but some commands make more sense using two or more words
- # ===Arguments
- # new_category::: A String to set the category to (see examples)
- # ===Examples:
- # Data bag commands would be in the 'data' category by default. To put them
- # in the 'data bag' category:
- # category('data bag')
+ # @param new_category [String] value to set the category to (see examples)
+ #
+ # @example Data bag commands would be in the 'data' category by default. To
+ # put them in the 'data bag' category:
+ # category('data bag')
def self.category(new_category)
@category = new_category
end
@@ -145,7 +153,7 @@ class Chef
end
def self.subcommand_class_from(args)
- if args.size == 1 && args[0].strip.casecmp("rehash").zero?
+ if args.size == 1 && args[0].strip.casecmp("rehash") == 0
# To prevent issues with the rehash file not pointing to the correct plugins,
# we always use the glob loader when regenerating the rehash file
@subcommand_loader = Chef::Knife::SubcommandLoader.gem_glob_loader(chef_config_dir)
@@ -178,11 +186,12 @@ class Chef
@config_loader ||= WorkstationConfigLoader.new(nil, Chef::Log)
end
- def self.load_config(explicit_config_file)
+ def self.load_config(explicit_config_file, profile)
config_loader.explicit_config_file = explicit_config_file
+ config_loader.profile = profile
config_loader.load
- ui.warn("No knife configuration file found") if config_loader.no_config_found?
+ ui.warn("No knife configuration file found. See https://docs.chef.io/config_rb/ for details.") if config_loader.no_config_found?
config_loader
rescue Exceptions::ConfigurationError => e
@@ -196,10 +205,11 @@ class Chef
# Run knife for the given +args+ (ARGV), adding +options+ to the list of
# CLI options that the subcommand knows how to handle.
- # ===Arguments
- # args::: usually ARGV
- # options::: A Mixlib::CLI option parser hash. These +options+ are how
- # subcommands know about global knife CLI options
+ #
+ # @param args [Array] The arguments. Usually ARGV
+ # @param options [Mixlib::CLI option parser hash] These +options+ are how
+ # subcommands know about global knife CLI options
+ #
def self.run(args, options = {})
# Fallback debug logging. Normally the logger isn't configured until we
# read the config, but this means any logging that happens before the
@@ -228,14 +238,27 @@ class Chef
end
def self.load_deps
- dependency_loaders.each do |dep_loader|
- dep_loader.call
- end
+ dependency_loaders.each(&:call)
end
- OFFICIAL_PLUGINS = %w{ec2 rackspace windows openstack terremark bluebox}
+ OFFICIAL_PLUGINS = %w{lpar openstack push rackspace vcenter}.freeze
class << self
+ def 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 /deprecated/i.match?(category)
+
+ 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
+
private
# @api private
@@ -243,25 +266,23 @@ class Chef
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.
# @api private
def subcommand_not_found!(args)
- ui.fatal("Cannot find subcommand 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)
+ if subcommand_loader.is_a?(Chef::Knife::SubcommandLoader::HashedCommandLoader)
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)
+ if CHEF_ORGANIZATION_MANAGEMENT.include?(args[0])
+ list_commands("CHEF ORGANIZATION MANAGEMENT")
+ elsif category_commands = guess_category(args)
list_commands(category_commands)
- elsif missing_plugin = ( OFFICIAL_PLUGINS.find { |plugin| plugin == args[0] } )
- ui.info("The #{missing_plugin} commands were moved to plugins in Chef 0.10")
- ui.info("You can install the plugin with `(sudo) gem install knife-#{missing_plugin}`")
- ui.info("Use `chef gem install knife-#{missing_plugin}` instead if using ChefDK")
+ elsif OFFICIAL_PLUGINS.include?(args[0]) # command was an uninstalled official chef knife plugin
+ ui.info("Use `#{ChefUtils::Dist::Infra::EXEC} gem install knife-#{args[0]}` to install the plugin into Chef Workstation")
else
list_commands
end
@@ -270,21 +291,6 @@ class Chef
end
# @api private
- def 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
-
- # @api private
def reset_config_path!
@@chef_config_dir = nil
end
@@ -308,15 +314,23 @@ class Chef
# 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("_")
- @name_args.reject! { |name_arg| command_name_words == name_arg }
+ command_name_joined = command_name_words.join("_")
+ @name_args.reject! { |name_arg| command_name_joined == name_arg }
+
+ # Similar handling for hyphens.
+ command_name_joined = command_name_words.join("-")
+ @name_args.reject! { |name_arg| command_name_joined == name_arg }
if config[:help]
msg opt_parser
exit 1
end
- # copy Mixlib::CLI over so that it can be configured in knife.rb
+ # Grab a copy before config merge occurs, so that we can later identify
+ # where a given config value is sourced from.
+ @original_config = config.dup
+
+ # copy Mixlib::CLI over so that it can be configured in config.rb/knife.rb
# config file
Chef::Config[:verbosity] = config[:verbosity] if config[:verbosity]
end
@@ -329,35 +343,65 @@ class Chef
exit(1)
end
- # keys from mixlib-cli options
- def cli_keys
- self.class.options.keys
+ # This is all set and default mixlib-config values. We only need the default
+ # values here (the set values are explicitly mixed in again later), but there is
+ # no mixlib-config API to get a Hash back with only the default values.
+ #
+ # Assumption: since config_file_defaults is the lowest precedence it doesn't matter
+ # that we include the set values here, but this is a hack and makes the name of the
+ # method a lie. FIXME: make the name not a lie by adding an API to mixlib-config.
+ #
+ # @api private
+ #
+ def config_file_defaults
+ Chef::Config[:knife].save(true) # this is like "dup" to a (real) Hash, and includes default values (and user set values)
end
- # extracts the settings from the Chef::Config[:knife] sub-hash that correspond
- # to knife cli options -- in preparation for merging config values with cli values
+ # This is only the user-set mixlib-config values. We do not include the defaults
+ # here so that the config defaults do not override the cli defaults.
+ #
+ # @api private
#
- # NOTE: due to weirdness in mixlib-config #has_key? is only true if the value has
- # been set by the user -- the Chef::Config defaults return #has_key?() of false and
- # this code DEPENDS on that functionality since applying the default values in
- # Chef::Config[:knife] would break the defaults in the cli that we would otherwise
- # overwrite.
def config_file_settings
- cli_keys.each_with_object({}) do |key, memo|
- memo[key] = Chef::Config[:knife][key] if Chef::Config[:knife].has_key?(key)
- end
+ Chef::Config[:knife].save(false) # this is like "dup" to a (real) Hash, and does not include default values (just user set values)
end
# config is merged in this order (inverse of precedence)
- # default_config - mixlib-cli defaults (accessor from the mixin)
- # config_file_settings - Chef::Config[:knife] sub-hash
- # config - mixlib-cli settings (accessor from the mixin)
+ # config_file_defaults - Chef::Config[:knife] defaults from chef-config (XXX: this also includes the settings, but they get overwritten)
+ # default_config - mixlib-cli defaults (accessor from mixlib-cli)
+ # config_file_settings - Chef::Config[:knife] user settings from the client.rb file
+ # config - mixlib-cli settings (accessor from mixlib-cli)
+ #
def merge_configs
+ # Update our original_config - if someone has created a knife command
+ # instance directly, they are likely ot have set cmd.config values directly
+ # as well, at which point our saved original config is no longer up to date.
+ @original_config = config.dup
# other code may have a handle to the config object, so use Hash#replace to deliberately
# update-in-place.
- config.replace(
- default_config.merge(config_file_settings).merge(config)
- )
+ config.replace(config_file_defaults.merge(default_config).merge(config_file_settings).merge(config))
+ end
+
+ #
+ # Determine the source of a given configuration key
+ #
+ # @argument key [Symbol] a configuration key
+ # @return [Symbol,NilClass] return the source of the config key,
+ # one of:
+ # - :cli - this was explicitly provided on the CLI
+ # - :config - this came from Chef::Config[:knife] explicitly being set
+ # - :cli_default - came from a declared CLI `option`'s `default` value.
+ # - :config_default - this came from Chef::Config[:knife]'s defaults
+ # - nil - if the key could not be found in any source.
+ # This can happen when it is invalid, or has been
+ # set directly into #config without then calling #merge_config
+ def config_source(key)
+ return :cli if @original_config.include? key
+ return :config if config_file_settings.key? key
+ return :cli_default if default_config.include? key
+ return :config_default if config_file_defaults.key? key # must come after :config check
+
+ nil
end
# Catch-all method that does any massaging needed for various config
@@ -371,22 +415,24 @@ class Chef
Chef::Config[:log_level] = :warn
when 1
Chef::Config[:log_level] = :info
- else
+ when 2
Chef::Config[:log_level] = :debug
+ else
+ Chef::Config[:log_level] = :trace
end
- Chef::Config[:log_level] = :debug if ENV["KNIFE_DEBUG"]
+ Chef::Config[:log_level] = :trace 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]
Chef::Config[:chef_server_url] = config[:chef_server_url] if config[:chef_server_url]
Chef::Config[:environment] = config[:environment] if config[:environment]
- Chef::Config.local_mode = config[:local_mode] if config.has_key?(:local_mode)
+ Chef::Config.local_mode = config[:local_mode] if config.key?(:local_mode)
- Chef::Config.listen = config[:listen] if config.has_key?(:listen)
+ Chef::Config.listen = config[:listen] if config.key?(:listen)
- if Chef::Config.local_mode && !Chef::Config.has_key?(:cookbook_path) && !Chef::Config.has_key?(:chef_repo_path)
+ if Chef::Config.local_mode && !Chef::Config.key?(:cookbook_path) && !Chef::Config.key?(:chef_repo_path)
Chef::Config.chef_repo_path = Chef::Config.find_chef_repo_path(Dir.pwd)
end
Chef::Config.chef_zero.host = config[:chef_zero_host] if config[:chef_zero_host]
@@ -406,31 +452,46 @@ class Chef
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_loader = self.class.load_config(config[:config_file], config[:profile])
config[:config_file] = config_loader.config_location
+ # For CLI options like `--config-option key=value`. These have to get
+ # parsed and applied separately.
+ extra_config_options = config.delete(:config_option)
+
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]
+
+ begin
+ Chef::Config.apply_extra_config_options(extra_config_options)
+ rescue ChefConfig::UnparsableConfigOption => e
+ ui.error e.message
+ show_usage
+ exit(1)
+ end
+
+ Chef::Config.export_proxies
end
def show_usage
- stdout.puts("USAGE: " + self.opt_parser.to_s)
+ stdout.puts("USAGE: " + opt_parser.to_s)
end
def run_with_pretty_exceptions(raise_exception = false)
- unless self.respond_to?(:run)
+ unless respond_to?(:run)
ui.error "You need to add a #run method to your knife command before you can use it"
end
- enforce_path_sanity
+ ENV["PATH"] = default_paths if Chef::Config[:enforce_default_paths] || Chef::Config[:enforce_path_sanity]
maybe_setup_fips
Chef::LocalMode.with_server_connectivity do
run
end
rescue Exception => e
- raise if raise_exception || Chef::Config[:verbosity] == 2
+ raise if raise_exception || ( Chef::Config[:verbosity] && Chef::Config[:verbosity] >= 2 )
+
humanize_exception(e)
exit 100
end
@@ -439,12 +500,12 @@ class Chef
case e
when SystemExit
raise # make sure exit passes through.
- when Net::HTTPServerException, Net::HTTPFatalError
+ when Net::HTTPClientException, Net::HTTPFatalError
humanize_http_exception(e)
when OpenSSL::SSL::SSLError
ui.error "Could not establish a secure connection to the server."
ui.info "Use `knife ssl check` to troubleshoot your SSL configuration."
- ui.info "If your Chef Server uses a self-signed certificate, you can use"
+ ui.info "If your server uses a self-signed certificate, you can use"
ui.info "`knife ssl fetch` to make knife trust the server's certificates."
ui.info ""
ui.info "Original Exception: #{e.class.name}: #{e.message}"
@@ -454,14 +515,14 @@ class Chef
when NameError, NoMethodError
ui.error "knife encountered an unexpected error"
ui.info "This may be a bug in the '#{self.class.common_name}' knife command or plugin"
- ui.info "Please collect the output of this command with the `-VV` option before filing a bug report."
+ ui.info "Please collect the output of this command with the `-VVV` option before filing a bug report."
ui.info "Exception: #{e.class.name}: #{e.message}"
when Chef::Exceptions::PrivateKeyMissing
ui.error "Your private key could not be loaded from #{api_key}"
ui.info "Check your configuration file and ensure that your private key is readable"
when Chef::Exceptions::InvalidRedirect
ui.error "Invalid Redirect: #{e.message}"
- ui.info "Change your server location in knife.rb to the server's FQDN to avoid unwanted redirections."
+ ui.info "Change your server location in config.rb/knife.rb to the server's FQDN to avoid unwanted redirections."
else
ui.error "#{e.class.name}: #{e.message}"
end
@@ -474,7 +535,11 @@ class Chef
ui.error "Failed to authenticate to #{server_url} as #{username} with key #{api_key}"
ui.info "Response: #{format_rest_error(response)}"
when Net::HTTPForbidden
- ui.error "You authenticated successfully to #{server_url} as #{username} but you are not authorized for this action"
+ ui.error "You authenticated successfully to #{server_url} as #{username} but you are not authorized for this action."
+ proxy_env_vars = ENV.to_hash.keys.map(&:downcase) & %w{http_proxy https_proxy ftp_proxy socks_proxy no_proxy}
+ unless proxy_env_vars.empty?
+ ui.error "There are proxy servers configured, your server url may need to be added to NO_PROXY."
+ end
ui.info "Response: #{format_rest_error(response)}"
when Net::HTTPBadRequest
ui.error "The data in your request was invalid"
@@ -496,10 +561,10 @@ class Chef
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"
+ ui.error "The API version that Knife is using is not supported by the server you sent this request to."
+ ui.info "The request that Knife sent was using API version #{client_api_version}."
+ ui.info "The server you sent the request to supports a min API version of #{min_server_version} and a max API version of #{max_server_version}."
+ ui.info "Please either update your #{ChefUtils::Dist::Infra::PRODUCT} or the server to be a compatible set."
else
ui.error response.message
ui.info "Response: #{format_rest_error(response)}"
@@ -526,7 +591,11 @@ class Chef
# FIXME: yard with @yield
def create_object(object, pretty_name = nil, object_class: nil)
- output = edit_data(object, object_class: object_class)
+ output = if object_class
+ edit_data(object, object_class: object_class)
+ else
+ edit_hash(object)
+ end
if Kernel.block_given?
output = yield(output)
@@ -536,7 +605,7 @@ class Chef
pretty_name ||= output
- self.msg("Created #{pretty_name}")
+ msg("Created #{pretty_name}")
output(output) if config[:print_after]
end
@@ -555,7 +624,7 @@ class Chef
output(format_for_display(object)) if config[:print_after]
obj_name = delete_name ? "#{delete_name}[#{name}]" : object
- self.msg("Deleted #{obj_name}")
+ msg("Deleted #{obj_name}")
end
# helper method for testing if a field exists
@@ -570,14 +639,14 @@ class Chef
def rest
@rest ||= begin
- require "chef/server_api"
+ require_relative "server_api"
Chef::ServerAPI.new(Chef::Config[:chef_server_url])
end
end
def noauth_rest
@rest ||= begin
- require "chef/http/simple_json"
+ require_relative "http/simple_json"
Chef::HTTP::SimpleJSON.new(Chef::Config[:chef_server_url])
end
end
@@ -587,7 +656,7 @@ class Chef
end
def maybe_setup_fips
- if !config[:fips].nil?
+ unless config[:fips].nil?
Chef::Config[:fips] = config[:fips]
end
Chef::Config.init_openssl
diff --git a/lib/chef/knife/acl_add.rb b/lib/chef/knife/acl_add.rb
new file mode 100644
index 0000000000..144a18fcb1
--- /dev/null
+++ b/lib/chef/knife/acl_add.rb
@@ -0,0 +1,57 @@
+#
+# Author:: Steven Danna (steve@chef.io)
+# Author:: Jeremiah Snapp (jeremiah@chef.io)
+# Copyright:: Copyright (c) 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_relative "../knife"
+
+class Chef
+ class Knife
+ class AclAdd < Chef::Knife
+ category "acl"
+ banner "knife acl add MEMBER_TYPE MEMBER_NAME OBJECT_TYPE OBJECT_NAME PERMS"
+
+ deps do
+ require_relative "acl_base"
+ include Chef::Knife::AclBase
+ end
+
+ def run
+ member_type, member_name, object_type, object_name, perms = name_args
+
+ if name_args.length != 5
+ show_usage
+ ui.fatal "You must specify the member type [client|group], member name, object type, object name and perms"
+ exit 1
+ end
+
+ unless %w{client group}.include?(member_type)
+ ui.fatal "ERROR: To enforce best practice, knife-acl can only add a client or a group to an ACL."
+ ui.fatal " See the knife-acl README for more information."
+ exit 1
+ end
+ validate_perm_type!(perms)
+ validate_member_name!(member_name)
+ validate_object_name!(object_name)
+ validate_object_type!(object_type)
+ validate_member_exists!(member_type, member_name)
+
+ add_to_acl!(member_type, member_name, object_type, object_name, perms)
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/acl_base.rb b/lib/chef/knife/acl_base.rb
new file mode 100644
index 0000000000..0835d1ac05
--- /dev/null
+++ b/lib/chef/knife/acl_base.rb
@@ -0,0 +1,183 @@
+#
+# Author:: Steven Danna (steve@chef.io)
+# Author:: Jeremiah Snapp (<jeremiah@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../knife"
+
+class Chef
+ class Knife
+ module AclBase
+
+ PERM_TYPES = %w{create read update delete grant}.freeze unless defined? PERM_TYPES
+ MEMBER_TYPES = %w{client group user}.freeze unless defined? MEMBER_TYPES
+ OBJECT_TYPES = %w{clients containers cookbooks data environments groups nodes roles policies policy_groups}.freeze unless defined? OBJECT_TYPES
+ OBJECT_NAME_SPEC = /^[\-[:alnum:]_\.]+$/.freeze unless defined? OBJECT_NAME_SPEC
+
+ def validate_object_type!(type)
+ unless OBJECT_TYPES.include?(type)
+ ui.fatal "Unknown object type \"#{type}\". The following types are permitted: #{OBJECT_TYPES.join(", ")}"
+ exit 1
+ end
+ end
+
+ def validate_object_name!(name)
+ unless OBJECT_NAME_SPEC.match(name)
+ ui.fatal "Invalid name: #{name}"
+ exit 1
+ end
+ end
+
+ def validate_member_type!(type)
+ unless MEMBER_TYPES.include?(type)
+ ui.fatal "Unknown member type \"#{type}\". The following types are permitted: #{MEMBER_TYPES.join(", ")}"
+ exit 1
+ end
+ end
+
+ def validate_member_name!(name)
+ # Same rules apply to objects and members
+ validate_object_name!(name)
+ end
+
+ def validate_perm_type!(perms)
+ perms.split(",").each do |perm|
+ unless PERM_TYPES.include?(perm)
+ ui.fatal "Invalid permission \"#{perm}\". The following permissions are permitted: #{PERM_TYPES.join(",")}"
+ exit 1
+ end
+ end
+ end
+
+ def validate_member_exists!(member_type, member_name)
+ true if rest.get_rest("#{member_type}s/#{member_name}")
+ rescue NameError
+ # ignore "NameError: uninitialized constant Chef::ApiClient" when finding a client
+ true
+ rescue
+ ui.fatal "#{member_type} '#{member_name}' does not exist"
+ exit 1
+ end
+
+ def is_usag?(gname)
+ gname.length == 32 && gname =~ /^[0-9a-f]+$/
+ end
+
+ def get_acl(object_type, object_name)
+ rest.get_rest("#{object_type}/#{object_name}/_acl?detail=granular")
+ end
+
+ def get_ace(object_type, object_name, perm)
+ get_acl(object_type, object_name)[perm]
+ end
+
+ def add_to_acl!(member_type, member_name, object_type, object_name, perms)
+ acl = get_acl(object_type, object_name)
+ perms.split(",").each do |perm|
+ ui.msg "Adding '#{member_name}' to '#{perm}' ACE of '#{object_name}'"
+ ace = acl[perm]
+
+ case member_type
+ when "client", "user"
+ # Our PUT body depends on the type of reply we get from _acl?detail=granular
+ # When the server replies with json attributes 'users' and 'clients',
+ # we'll want to modify entries under the same keys they arrived.- their presence
+ # in the body tells us that CS will accept them in a PUT.
+ # Older version of chef-server will continue to use 'actors' for a combined list
+ # and expect the same in the body.
+ key = "#{member_type}s"
+ key = "actors" unless ace.key? key
+ next if ace[key].include?(member_name)
+
+ ace[key] << member_name
+ when "group"
+ next if ace["groups"].include?(member_name)
+
+ ace["groups"] << member_name
+ end
+
+ update_ace!(object_type, object_name, perm, ace)
+ end
+ end
+
+ def remove_from_acl!(member_type, member_name, object_type, object_name, perms)
+ acl = get_acl(object_type, object_name)
+ perms.split(",").each do |perm|
+ ui.msg "Removing '#{member_name}' from '#{perm}' ACE of '#{object_name}'"
+ ace = acl[perm]
+
+ case member_type
+ when "client", "user"
+ key = "#{member_type}s"
+ key = "actors" unless ace.key? key
+ next unless ace[key].include?(member_name)
+
+ ace[key].delete(member_name)
+ when "group"
+ next unless ace["groups"].include?(member_name)
+
+ ace["groups"].delete(member_name)
+ end
+
+ update_ace!(object_type, object_name, perm, ace)
+ end
+ end
+
+ def update_ace!(object_type, object_name, ace_type, ace)
+ rest.put_rest("#{object_type}/#{object_name}/_acl/#{ace_type}", ace_type => ace)
+ end
+
+ def add_to_group!(member_type, member_name, group_name)
+ validate_member_exists!(member_type, member_name)
+ existing_group = rest.get_rest("groups/#{group_name}")
+ ui.msg "Adding '#{member_name}' to '#{group_name}' group"
+ unless existing_group["#{member_type}s"].include?(member_name)
+ existing_group["#{member_type}s"] << member_name
+ new_group = {
+ "groupname" => existing_group["groupname"],
+ "orgname" => existing_group["orgname"],
+ "actors" => {
+ "users" => existing_group["users"],
+ "clients" => existing_group["clients"],
+ "groups" => existing_group["groups"],
+ },
+ }
+ rest.put_rest("groups/#{group_name}", new_group)
+ end
+ end
+
+ def remove_from_group!(member_type, member_name, group_name)
+ validate_member_exists!(member_type, member_name)
+ existing_group = rest.get_rest("groups/#{group_name}")
+ ui.msg "Removing '#{member_name}' from '#{group_name}' group"
+ if existing_group["#{member_type}s"].include?(member_name)
+ existing_group["#{member_type}s"].delete(member_name)
+ new_group = {
+ "groupname" => existing_group["groupname"],
+ "orgname" => existing_group["orgname"],
+ "actors" => {
+ "users" => existing_group["users"],
+ "clients" => existing_group["clients"],
+ "groups" => existing_group["groups"],
+ },
+ }
+ rest.put_rest("groups/#{group_name}", new_group)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/acl_bulk_add.rb b/lib/chef/knife/acl_bulk_add.rb
new file mode 100644
index 0000000000..4992fe2afa
--- /dev/null
+++ b/lib/chef/knife/acl_bulk_add.rb
@@ -0,0 +1,78 @@
+#
+# Author:: Jeremiah Snapp (jeremiah@chef.io)
+# Copyright:: Copyright (c) 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_relative "../knife"
+
+class Chef
+ class Knife
+ class AclBulkAdd < Chef::Knife
+ category "acl"
+ banner "knife acl bulk add MEMBER_TYPE MEMBER_NAME OBJECT_TYPE REGEX PERMS"
+
+ deps do
+ require_relative "acl_base"
+ include Chef::Knife::AclBase
+ end
+
+ def run
+ member_type, member_name, object_type, regex, perms = name_args
+ object_name_matcher = /#{regex}/
+
+ if name_args.length != 5
+ show_usage
+ ui.fatal "You must specify the member type [client|group], member name, object type, object name REGEX and perms"
+ exit 1
+ end
+
+ unless %w{client group}.include?(member_type)
+ ui.fatal "ERROR: To enforce best practice, knife-acl can only add a client or a group to an ACL."
+ ui.fatal " See the knife-acl README for more information."
+ exit 1
+ end
+ validate_perm_type!(perms)
+ validate_member_name!(member_name)
+ validate_object_type!(object_type)
+ validate_member_exists!(member_type, member_name)
+
+ if %w{containers groups}.include?(object_type)
+ ui.fatal "bulk modifying the ACL of #{object_type} is not permitted"
+ exit 1
+ end
+
+ objects_to_modify = []
+ all_objects = rest.get_rest(object_type)
+ objects_to_modify = all_objects.keys.select { |object_name| object_name =~ object_name_matcher }
+
+ if objects_to_modify.empty?
+ ui.info "No #{object_type} match the expression /#{regex}/"
+ exit 0
+ end
+
+ ui.msg("The ACL of the following #{object_type} will be modified:")
+ ui.msg("")
+ ui.msg(ui.list(objects_to_modify.sort, :columns_down))
+ ui.msg("")
+ ui.confirm("Are you sure you want to modify the ACL of these #{object_type}?")
+
+ objects_to_modify.each do |object_name|
+ add_to_acl!(member_type, member_name, object_type, object_name, perms)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/acl_bulk_remove.rb b/lib/chef/knife/acl_bulk_remove.rb
new file mode 100644
index 0000000000..0f35f1e2fb
--- /dev/null
+++ b/lib/chef/knife/acl_bulk_remove.rb
@@ -0,0 +1,83 @@
+#
+# Author:: Jeremiah Snapp (jeremiah@chef.io)
+# Copyright:: Copyright (c) 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_relative "../knife"
+
+class Chef
+ class Knife
+ class AclBulkRemove < Chef::Knife
+ category "acl"
+ banner "knife acl bulk remove MEMBER_TYPE MEMBER_NAME OBJECT_TYPE REGEX PERMS"
+
+ deps do
+ require_relative "acl_base"
+ include Chef::Knife::AclBase
+ end
+
+ def run
+ member_type, member_name, object_type, regex, perms = name_args
+ object_name_matcher = /#{regex}/
+
+ if name_args.length != 5
+ show_usage
+ ui.fatal "You must specify the member type [client|group|user], member name, object type, object name REGEX and perms"
+ exit 1
+ end
+
+ if member_name == "pivotal" && %w{client user}.include?(member_type)
+ ui.fatal "ERROR: 'pivotal' is a system user so knife-acl will not remove it from an ACL."
+ exit 1
+ end
+ if member_name == "admins" && member_type == "group" && perms.to_s.split(",").include?("grant")
+ ui.fatal "ERROR: knife-acl will not remove the 'admins' group from the 'grant' ACE."
+ ui.fatal " Removal could prevent future attempts to modify permissions."
+ exit 1
+ end
+ validate_perm_type!(perms)
+ validate_member_type!(member_type)
+ validate_member_name!(member_name)
+ validate_object_type!(object_type)
+ validate_member_exists!(member_type, member_name)
+
+ if %w{containers groups}.include?(object_type)
+ ui.fatal "bulk modifying the ACL of #{object_type} is not permitted"
+ exit 1
+ end
+
+ objects_to_modify = []
+ all_objects = rest.get_rest(object_type)
+ objects_to_modify = all_objects.keys.select { |object_name| object_name =~ object_name_matcher }
+
+ if objects_to_modify.empty?
+ ui.info "No #{object_type} match the expression /#{regex}/"
+ exit 0
+ end
+
+ ui.msg("The ACL of the following #{object_type} will be modified:")
+ ui.msg("")
+ ui.msg(ui.list(objects_to_modify.sort, :columns_down))
+ ui.msg("")
+ ui.confirm("Are you sure you want to modify the ACL of these #{object_type}?")
+
+ objects_to_modify.each do |object_name|
+ remove_from_acl!(member_type, member_name, object_type, object_name, perms)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/acl_remove.rb b/lib/chef/knife/acl_remove.rb
new file mode 100644
index 0000000000..13f089ff3e
--- /dev/null
+++ b/lib/chef/knife/acl_remove.rb
@@ -0,0 +1,62 @@
+#
+# Author:: Steven Danna (steve@chef.io)
+# Author:: Jeremiah Snapp (jeremiah@chef.io)
+# Copyright:: Copyright (c) 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_relative "../knife"
+
+class Chef
+ class Knife
+ class AclRemove < Chef::Knife
+ category "acl"
+ banner "knife acl remove MEMBER_TYPE MEMBER_NAME OBJECT_TYPE OBJECT_NAME PERMS"
+
+ deps do
+ require_relative "acl_base"
+ include Chef::Knife::AclBase
+ end
+
+ def run
+ member_type, member_name, object_type, object_name, perms = name_args
+
+ if name_args.length != 5
+ show_usage
+ ui.fatal "You must specify the member type [client|group|user], member name, object type, object name and perms"
+ exit 1
+ end
+
+ if member_name == "pivotal" && %w{client user}.include?(member_type)
+ ui.fatal "ERROR: 'pivotal' is a system user so knife-acl will not remove it from an ACL."
+ exit 1
+ end
+ if member_name == "admins" && member_type == "group" && perms.to_s.split(",").include?("grant")
+ ui.fatal "ERROR: knife-acl will not remove the 'admins' group from the 'grant' ACE."
+ ui.fatal " Removal could prevent future attempts to modify permissions."
+ exit 1
+ end
+ validate_perm_type!(perms)
+ validate_member_type!(member_type)
+ validate_member_name!(member_name)
+ validate_object_name!(object_name)
+ validate_object_type!(object_type)
+ validate_member_exists!(member_type, member_name)
+
+ remove_from_acl!(member_type, member_name, object_type, object_name, perms)
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/acl_show.rb b/lib/chef/knife/acl_show.rb
new file mode 100644
index 0000000000..d3a5002b30
--- /dev/null
+++ b/lib/chef/knife/acl_show.rb
@@ -0,0 +1,56 @@
+#
+# Author:: Steven Danna (steve@chef.io)
+# Copyright:: Copyright (c) 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_relative "../knife"
+
+class Chef
+ class Knife
+ class AclShow < Chef::Knife
+ category "acl"
+ banner "knife acl show OBJECT_TYPE OBJECT_NAME"
+
+ deps do
+ require_relative "acl_base"
+ include Chef::Knife::AclBase
+ end
+
+ def run
+ object_type, object_name = name_args
+
+ if name_args.length != 2
+ show_usage
+ ui.fatal "You must specify an object type and object name"
+ exit 1
+ end
+
+ validate_object_type!(object_type)
+ validate_object_name!(object_name)
+ acl = get_acl(object_type, object_name)
+ PERM_TYPES.each do |perm|
+ # Filter out the actors field if we have
+ # users and clients. Note that if one is present,
+ # both will be - but we're checking both for completeness.
+ if acl[perm].key?("users") && acl[perm].key?("clients")
+ acl[perm].delete "actors"
+ end
+ end
+ ui.output acl
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb
index ee4d9ce7af..1550c62dc1 100644
--- a/lib/chef/knife/bootstrap.rb
+++ b/lib/chef/knife/bootstrap.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,268 +16,446 @@
# 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_relative "../knife"
+require_relative "data_bag_secret_options"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+require "license_acceptance/cli_flags/mixlib_cli"
+module LicenseAcceptance
+ autoload :Acceptor, "license_acceptance/acceptor"
+end
class Chef
class Knife
class Bootstrap < Knife
include DataBagSecretOptions
-
- attr_accessor :client_builder
- 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"
- Chef::Knife::Ssh.load_deps
- end
-
- banner "knife bootstrap [SSH_USER@]FQDN (options)"
-
- option :ssh_user,
- :short => "-x USERNAME",
- :long => "--ssh-user USERNAME",
- :description => "The ssh username",
- :default => "root"
-
- option :ssh_password,
- :short => "-P PASSWORD",
- :long => "--ssh-password PASSWORD",
- :description => "The ssh password"
-
- option :ssh_port,
- :short => "-p PORT",
- :long => "--ssh-port PORT",
- :description => "The ssh port",
- :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key }
-
+ include LicenseAcceptance::CLIFlags::MixlibCLI
+
+ SUPPORTED_CONNECTION_PROTOCOLS ||= %w{ssh winrm}.freeze
+ WINRM_AUTH_PROTOCOL_LIST ||= %w{plaintext kerberos ssl negotiate}.freeze
+
+ # Common connectivity options
+ option :connection_user,
+ short: "-U USERNAME",
+ long: "--connection-user USERNAME",
+ description: "Authenticate to the target host with this user account."
+
+ option :connection_password,
+ short: "-P PASSWORD",
+ long: "--connection-password PASSWORD",
+ description: "Authenticate to the target host with this password."
+
+ option :connection_port,
+ short: "-p PORT",
+ long: "--connection-port PORT",
+ description: "The port on the target node to connect to."
+
+ option :connection_protocol,
+ short: "-o PROTOCOL",
+ long: "--connection-protocol PROTOCOL",
+ description: "The protocol to use to connect to the target node.",
+ in: SUPPORTED_CONNECTION_PROTOCOLS
+
+ option :max_wait,
+ short: "-W SECONDS",
+ long: "--max-wait SECONDS",
+ description: "The maximum time to wait for the initial connection to be established."
+
+ option :session_timeout,
+ long: "--session-timeout SECONDS",
+ description: "The number of seconds to wait for each connection operation to be acknowledged while running bootstrap.",
+ default: 60
+
+ # WinRM Authentication
+ option :winrm_ssl_peer_fingerprint,
+ long: "--winrm-ssl-peer-fingerprint FINGERPRINT",
+ description: "SSL certificate fingerprint expected from the target."
+
+ option :ca_trust_file,
+ short: "-f CA_TRUST_PATH",
+ long: "--ca-trust-file CA_TRUST_PATH",
+ description: "The Certificate Authority (CA) trust file used for SSL transport."
+
+ option :winrm_no_verify_cert,
+ long: "--winrm-no-verify-cert",
+ description: "Do not verify the SSL certificate of the target node for WinRM.",
+ boolean: true
+
+ option :winrm_ssl,
+ long: "--winrm-ssl",
+ description: "Use SSL in the WinRM connection."
+
+ option :winrm_auth_method,
+ short: "-w AUTH-METHOD",
+ long: "--winrm-auth-method AUTH-METHOD",
+ description: "The WinRM authentication method to use.",
+ in: WINRM_AUTH_PROTOCOL_LIST
+
+ option :winrm_basic_auth_only,
+ long: "--winrm-basic-auth-only",
+ description: "For WinRM basic authentication when using the 'ssl' auth method.",
+ boolean: true
+
+ # This option was provided in knife bootstrap windows winrm,
+ # but it is ignored in knife-windows/WinrmSession, and so remains unimplemented here.
+ # option :kerberos_keytab_file,
+ # :short => "-T KEYTAB_FILE",
+ # :long => "--keytab-file KEYTAB_FILE",
+ # :description => "The Kerberos keytab file used for authentication"
+
+ option :kerberos_realm,
+ short: "-R KERBEROS_REALM",
+ long: "--kerberos-realm KERBEROS_REALM",
+ description: "The Kerberos realm used for authentication."
+
+ option :kerberos_service,
+ short: "-S KERBEROS_SERVICE",
+ long: "--kerberos-service KERBEROS_SERVICE",
+ description: "The Kerberos service used for authentication."
+
+ ## SSH Authentication
option :ssh_gateway,
- :short => "-G GATEWAY",
- :long => "--ssh-gateway GATEWAY",
- :description => "The ssh gateway",
- :proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway] = key }
+ short: "-G GATEWAY",
+ long: "--ssh-gateway GATEWAY",
+ description: "The SSH gateway."
- option :forward_agent,
- :short => "-A",
- :long => "--forward-agent",
- :description => "Enable SSH agent forwarding",
- :boolean => true
+ option :ssh_gateway_identity,
+ long: "--ssh-gateway-identity SSH_GATEWAY_IDENTITY",
+ description: "The SSH identity file used for gateway authentication."
- option :identity_file,
- :long => "--identity-file IDENTITY_FILE",
- :description => "The SSH identity file used for authentication. [DEPRECATED] Use --ssh-identity-file instead."
+ option :ssh_forward_agent,
+ short: "-A",
+ long: "--ssh-forward-agent",
+ description: "Enable SSH agent forwarding.",
+ boolean: true
option :ssh_identity_file,
- :short => "-i IDENTITY_FILE",
- :long => "--ssh-identity-file IDENTITY_FILE",
- :description => "The SSH identity file used for authentication"
+ short: "-i IDENTITY_FILE",
+ long: "--ssh-identity-file IDENTITY_FILE",
+ description: "The SSH identity file used for authentication."
- option :chef_node_name,
- :short => "-N NAME",
- :long => "--node-name NAME",
- :description => "The Chef node name for your new node"
+ option :ssh_verify_host_key,
+ long: "--ssh-verify-host-key VALUE",
+ description: "Verify host key. Default is 'always'.",
+ in: %w{always accept_new accept_new_or_local_tunnel never},
+ default: "always"
- option :prerelease,
- :long => "--prerelease",
- :description => "Install the pre-release chef gems"
+ #
+ # bootstrap options
+ #
+ # client.rb content via chef-full/bootstrap_context
option :bootstrap_version,
- :long => "--bootstrap-version VERSION",
- :description => "The version of Chef to install",
- :proc => lambda { |v| Chef::Config[:knife][:bootstrap_version] = v }
+ long: "--bootstrap-version VERSION",
+ description: "The version of #{ChefUtils::Dist::Infra::PRODUCT} to install."
+
+ option :channel,
+ long: "--channel CHANNEL",
+ description: "Install from the given channel. Default is 'stable'.",
+ default: "stable",
+ in: %w{stable current unstable}
+ # client.rb content via chef-full/bootstrap_context
option :bootstrap_proxy,
- :long => "--bootstrap-proxy PROXY_URL",
- :description => "The proxy server for the node being bootstrapped",
- :proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p }
+ long: "--bootstrap-proxy PROXY_URL",
+ description: "The proxy server for the node being bootstrapped."
+ # client.rb content via bootstrap_context
option :bootstrap_proxy_user,
- :long => "--bootstrap-proxy-user PROXY_USER",
- :description => "The proxy authentication username for the node being bootstrapped"
+ long: "--bootstrap-proxy-user PROXY_USER",
+ description: "The proxy authentication username for the node being bootstrapped."
+ # client.rb content via bootstrap_context
option :bootstrap_proxy_pass,
- :long => "--bootstrap-proxy-pass PROXY_PASS",
- :description => "The proxy authentication password for the node being bootstrapped"
+ long: "--bootstrap-proxy-pass PROXY_PASS",
+ description: "The proxy authentication password for the node being bootstrapped."
+ # client.rb content via bootstrap_context
option :bootstrap_no_proxy,
- :long => "--bootstrap-no-proxy [NO_PROXY_URL|NO_PROXY_IP]",
- :description => "Do not proxy locations for the node being bootstrapped; this option is used internally by Opscode",
- :proc => Proc.new { |np| Chef::Config[:knife][:bootstrap_no_proxy] = np }
-
- # DEPR: Remove this option in Chef 13
- option :distro,
- :short => "-d DISTRO",
- :long => "--distro DISTRO",
- :description => "Bootstrap a distro using a template. [DEPRECATED] Use -t / --bootstrap-template option instead.",
- :proc => Proc.new { |v|
- Chef::Log.warn("[DEPRECATED] -d / --distro option is deprecated. Use -t / --bootstrap-template option instead.")
+ long: "--bootstrap-no-proxy [NO_PROXY_URL|NO_PROXY_IP]",
+ description: "Do not proxy locations for the node being bootstrapped"
+
+ # client.rb content via bootstrap_context
+ option :bootstrap_template,
+ short: "-t TEMPLATE",
+ long: "--bootstrap-template TEMPLATE",
+ description: "Bootstrap #{ChefUtils::Dist::Infra::PRODUCT} using a built-in or custom template. Set to the full path of an erb template or use one of the built-in templates."
+
+ # client.rb content via bootstrap_context
+ option :node_ssl_verify_mode,
+ long: "--node-ssl-verify-mode [peer|none]",
+ description: "Whether or not to verify the SSL cert for all HTTPS requests.",
+ proc: Proc.new { |v|
+ valid_values = %w{none peer}
+ unless valid_values.include?(v)
+ raise "Invalid value '#{v}' for --node-ssl-verify-mode. Valid values are: #{valid_values.join(", ")}"
+ end
+
v
}
- option :bootstrap_template,
- :short => "-t TEMPLATE",
- :long => "--bootstrap-template TEMPLATE",
- :description => "Bootstrap Chef using a built-in or custom template. Set to the full path of an erb template or use one of the built-in templates."
+ # bootstrap_context - client.rb
+ option :node_verify_api_cert,
+ long: "--[no-]node-verify-api-cert",
+ description: "Verify the SSL cert for HTTPS requests to the #{ChefUtils::Dist::Server::PRODUCT} API.",
+ boolean: true
+ # runtime - sudo settings (train handles sudo)
option :use_sudo,
- :long => "--sudo",
- :description => "Execute the bootstrap via sudo",
- :boolean => true
+ long: "--sudo",
+ description: "Execute the bootstrap via sudo.",
+ boolean: true
+ # runtime - sudo settings (train handles sudo)
option :preserve_home,
- :long => "--sudo-preserve-home",
- :description => "Preserve non-root user HOME environment variable with sudo",
- :boolean => true
+ long: "--sudo-preserve-home",
+ description: "Preserve non-root user HOME environment variable with sudo.",
+ boolean: true
+ # runtime - sudo settings (train handles sudo)
option :use_sudo_password,
- :long => "--use-sudo-password",
- :description => "Execute the bootstrap via sudo with password",
- :boolean => false
-
- # DEPR: Remove this option in Chef 13
- option :template_file,
- :long => "--template-file TEMPLATE",
- :description => "Full path to location of template to use. [DEPRECATED] Use -t / --bootstrap-template option instead.",
- :proc => Proc.new { |v|
- Chef::Log.warn("[DEPRECATED] --template-file option is deprecated. Use -t / --bootstrap-template option instead.")
- v
- }
+ long: "--use-sudo-password",
+ description: "Execute the bootstrap via sudo with password.",
+ boolean: false
+
+ # runtime - client_builder
+ option :chef_node_name,
+ short: "-N NAME",
+ long: "--node-name NAME",
+ description: "The node name for your new node."
+ # runtime - client_builder - set runlist when creating node
option :run_list,
- :short => "-r RUN_LIST",
- :long => "--run-list RUN_LIST",
- :description => "Comma separated list of roles/recipes to apply",
- :proc => lambda { |o| o.split(/[\s,]+/) },
- :default => []
+ short: "-r RUN_LIST",
+ long: "--run-list RUN_LIST",
+ description: "Comma separated list of roles/recipes to apply.",
+ proc: lambda { |o| o.split(/[\s,]+/) },
+ default: []
+ # runtime - client_builder - set policy name when creating node
option :policy_name,
- :long => "--policy-name POLICY_NAME",
- :description => "Policyfile name to use (--policy-group must also be given)",
- :default => nil
+ long: "--policy-name POLICY_NAME",
+ description: "Policyfile name to use (--policy-group must also be given).",
+ default: nil
+ # runtime - client_builder - set policy group when creating node
option :policy_group,
- :long => "--policy-group POLICY_GROUP",
- :description => "Policy group name to use (--policy-name must also be given)",
- :default => nil
+ long: "--policy-group POLICY_GROUP",
+ description: "Policy group name to use (--policy-name must also be given).",
+ default: nil
+ # runtime - client_builder - node tags
option :tags,
- :long => "--tags TAGS",
- :description => "Comma separated list of tags to apply to the node",
- :proc => lambda { |o| o.split(/[\s,]+/) },
- :default => []
+ long: "--tags TAGS",
+ description: "Comma separated list of tags to apply to the node.",
+ proc: lambda { |o| o.split(/[\s,]+/) },
+ default: []
+ # bootstrap template
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 => nil
+ short: "-j JSON_ATTRIBS",
+ long: "--json-attributes",
+ description: "A JSON string to be added to the first run of #{ChefUtils::Dist::Infra::CLIENT}.",
+ proc: lambda { |o| Chef::JSONCompat.parse(o) },
+ default: nil
+ # bootstrap template
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",
- :description => "Verify host key, enabled by default.",
- :boolean => true,
- :default => true
-
- option :hint,
- :long => "--hint HINT_NAME[=HINT_FILE]",
- :description => "Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.",
- :proc => Proc.new { |h|
- Chef::Config[:knife][:hints] ||= Hash.new
- name, path = h.split("=")
- Chef::Config[:knife][:hints][name] = path ? Chef::JSONCompat.parse(::File.read(path)) : Hash.new
+ long: "--json-attribute-file FILE",
+ description: "A JSON file to be used to the first run of #{ChefUtils::Dist::Infra::CLIENT}.",
+ proc: lambda { |o| Chef::JSONCompat.parse(File.read(o)) },
+ default: nil
+
+ # bootstrap template
+ # Create ohai hints in /etc/chef/ohai/hints, fname=hintname, content=value
+ option :hints,
+ long: "--hint HINT_NAME[=HINT_FILE]",
+ description: "Specify an Ohai hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.",
+ proc: Proc.new { |hint, accumulator|
+ accumulator ||= {}
+ name, path = hint.split("=", 2)
+ accumulator[name] = path ? Chef::JSONCompat.parse(::File.read(path)) : {}
+ accumulator
}
+ # bootstrap override: url of a an installer shell script to use in place of omnitruck
+ # Note that the bootstrap template _only_ references this out of Chef::Config, and not from
+ # the provided options to knife bootstrap, so we set the Chef::Config option here.
option :bootstrap_url,
- :long => "--bootstrap-url URL",
- :description => "URL to a custom installation script",
- :proc => Proc.new { |u| Chef::Config[:knife][:bootstrap_url] = u }
+ long: "--bootstrap-url URL",
+ description: "URL to a custom installation script."
+
+ option :bootstrap_product,
+ long: "--bootstrap-product PRODUCT",
+ description: "Product to install.",
+ default: "chef"
+ option :msi_url, # Windows target only
+ short: "-m URL",
+ long: "--msi-url URL",
+ description: "Location of the #{ChefUtils::Dist::Infra::PRODUCT} MSI. The default templates will prefer to download from this location. The MSI will be downloaded from #{ChefUtils::Dist::Org::WEBSITE} if not provided (Windows).",
+ default: ""
+
+ # bootstrap override: Do this instead of our own setup.sh from omnitruck. Causes bootstrap_url to be ignored.
option :bootstrap_install_command,
- :long => "--bootstrap-install-command COMMANDS",
- :description => "Custom command to install chef-client",
- :proc => Proc.new { |ic| Chef::Config[:knife][:bootstrap_install_command] = ic }
+ long: "--bootstrap-install-command COMMANDS",
+ description: "Custom command to install #{ChefUtils::Dist::Infra::PRODUCT}."
+
+ # bootstrap template: Run this command first in the bootstrap script
+ option :bootstrap_preinstall_command,
+ long: "--bootstrap-preinstall-command COMMANDS",
+ description: "Custom commands to run before installing #{ChefUtils::Dist::Infra::PRODUCT}."
+ # bootstrap template
option :bootstrap_wget_options,
- :long => "--bootstrap-wget-options OPTIONS",
- :description => "Add options to wget when installing chef-client",
- :proc => Proc.new { |wo| Chef::Config[:knife][:bootstrap_wget_options] = wo }
+ long: "--bootstrap-wget-options OPTIONS",
+ description: "Add options to wget when installing #{ChefUtils::Dist::Infra::PRODUCT}."
+ # bootstrap template
option :bootstrap_curl_options,
- :long => "--bootstrap-curl-options OPTIONS",
- :description => "Add options to curl when install chef-client",
- :proc => Proc.new { |co| Chef::Config[:knife][:bootstrap_curl_options] = co }
-
- option :node_ssl_verify_mode,
- :long => "--node-ssl-verify-mode [peer|none]",
- :description => "Whether or not to verify the SSL cert for all HTTPS requests.",
- :proc => Proc.new { |v|
- valid_values = %w{none peer}
- unless valid_values.include?(v)
- raise "Invalid value '#{v}' for --node-ssl-verify-mode. Valid values are: #{valid_values.join(", ")}"
- end
- v
- }
-
- option :node_verify_api_cert,
- :long => "--[no-]node-verify-api-cert",
- :description => "Verify the SSL cert for HTTPS requests to the Chef server API.",
- :boolean => true
+ long: "--bootstrap-curl-options OPTIONS",
+ description: "Add options to curl when install #{ChefUtils::Dist::Infra::PRODUCT}."
+ # chef_vault_handler
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."
+ # chef_vault_handler
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."
+ # chef_vault_handler
option :bootstrap_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(/:/)
- Chef::Config[:knife][:bootstrap_vault_item] ||= {}
- Chef::Config[:knife][:bootstrap_vault_item][vault] ||= []
- Chef::Config[:knife][:bootstrap_vault_item][vault].push(item)
- Chef::Config[:knife][:bootstrap_vault_item]
+ long: "--bootstrap-vault-item VAULT_ITEM",
+ description: 'A single vault and item to update as "vault:item".',
+ proc: Proc.new { |i, accumulator|
+ (vault, item) = i.split(":")
+ accumulator ||= {}
+ accumulator[vault] ||= []
+ accumulator[vault].push(item)
+ accumulator
}
- def initialize(argv = [])
- super
- @client_builder = Chef::Knife::Bootstrap::ClientBuilder.new(
+ # Deprecated options. These must be declared after
+ # regular options because they refer to the replacement
+ # option definitions implicitly.
+ deprecated_option :auth_timeout,
+ replacement: :max_wait,
+ long: "--max-wait SECONDS"
+
+ deprecated_option :forward_agent,
+ replacement: :ssh_forward_agent,
+ boolean: true, long: "--forward-agent"
+
+ deprecated_option :host_key_verify,
+ replacement: :ssh_verify_host_key,
+ boolean: true, long: "--[no-]host-key-verify",
+ value_mapper: Proc.new { |verify| verify ? "always" : "never" }
+
+ deprecated_option :prerelease,
+ replacement: :channel,
+ long: "--prerelease",
+ boolean: true, value_mapper: Proc.new { "current" }
+
+ deprecated_option :ssh_user,
+ replacement: :connection_user,
+ long: "--ssh-user USERNAME"
+
+ deprecated_option :ssh_password,
+ replacement: :connection_password,
+ long: "--ssh-password PASSWORD"
+
+ deprecated_option :ssh_port,
+ replacement: :connection_port,
+ long: "--ssh-port PASSWORD"
+
+ deprecated_option :ssl_peer_fingerprint,
+ replacement: :winrm_ssl_peer_fingerprint,
+ long: "--ssl-peer-fingerprint FINGERPRINT"
+
+ deprecated_option :winrm_user,
+ replacement: :connection_user,
+ long: "--winrm-user USERNAME", short: "-x USERNAME"
+
+ deprecated_option :winrm_password,
+ replacement: :connection_password,
+ long: "--winrm-password PASSWORD"
+
+ deprecated_option :winrm_port,
+ replacement: :connection_port,
+ long: "--winrm-port PORT"
+
+ deprecated_option :winrm_authentication_protocol,
+ replacement: :winrm_auth_method,
+ long: "--winrm-authentication-protocol PROTOCOL"
+
+ deprecated_option :winrm_session_timeout,
+ replacement: :session_timeout,
+ long: "--winrm-session-timeout MINUTES"
+
+ deprecated_option :winrm_ssl_verify_mode,
+ replacement: :winrm_no_verify_cert,
+ long: "--winrm-ssl-verify-mode MODE"
+
+ deprecated_option :winrm_transport, replacement: :winrm_ssl,
+ long: "--winrm-transport TRANSPORT",
+ value_mapper: Proc.new { |value| value == "ssl" }
+
+ attr_reader :connection
+
+ deps do
+ require "erubis" unless defined?(Erubis)
+
+ require "net/ssh" unless defined?(Net::SSH)
+ require_relative "../json_compat"
+ require_relative "../util/path_helper"
+ require_relative "bootstrap/chef_vault_handler"
+ require_relative "bootstrap/client_builder"
+ require_relative "bootstrap/train_connector"
+ end
+
+ banner "knife bootstrap [PROTOCOL://][USER@]FQDN (options)"
+
+ def client_builder
+ @client_builder ||= Chef::Knife::Bootstrap::ClientBuilder.new(
chef_config: Chef::Config,
- knife_config: config,
+ config: config,
ui: ui
)
- @chef_vault_handler = Chef::Knife::Bootstrap::ChefVaultHandler.new(
- knife_config: config,
+ end
+
+ def chef_vault_handler
+ @chef_vault_handler ||= Chef::Knife::Bootstrap::ChefVaultHandler.new(
+ config: config,
ui: ui
)
end
- # The default bootstrap template to use to bootstrap a server This is a public API hook
- # which knife plugins use or inherit and override.
+ # Determine if we need to accept the Chef Infra license locally in order to successfully bootstrap
+ # the remote node. Remote 'chef-client' run will fail if it is >= 15 and the license is not accepted locally.
+ def check_license
+ Chef::Log.debug("Checking if we need to accept Chef license to bootstrap node")
+ version = config[:bootstrap_version] || Chef::VERSION.split(".").first
+ acceptor = LicenseAcceptance::Acceptor.new(logger: Chef::Log, provided: Chef::Config[:chef_license])
+ if acceptor.license_required?("chef", version)
+ Chef::Log.debug("License acceptance required for chef version: #{version}")
+ license_id = acceptor.id_from_mixlib("chef")
+ acceptor.check_and_persist(license_id, version)
+ Chef::Config[:chef_license] ||= acceptor.acceptance_value
+ end
+ end
+
+ # The default bootstrap template to use to bootstrap a server.
+ # This is a public API hook which knife plugins use or inherit and override.
#
# @return [String] Default bootstrap template
def default_bootstrap_template
- "chef-full"
+ if connection.windows?
+ "windows-chef-client-msi"
+ else
+ "chef-full"
+ end
end
def host_descriptor
@@ -295,39 +473,32 @@ class Chef
end
end
- def user_name
- if host_descriptor
- @user_name ||= host_descriptor.split("@").reverse[1]
- end
- end
-
+ # @return [String] The CLI specific bootstrap template or the default
def bootstrap_template
- # The order here is important. We want to check if we have the new Chef 12 option is set first.
- # Knife cloud plugins unfortunately all set a default option for the :distro so it should be at
- # the end.
- config[:bootstrap_template] || config[:template_file] || config[:distro] || default_bootstrap_template
+ # Allow passing a bootstrap template or use the default
+ config[:bootstrap_template] || default_bootstrap_template
end
def find_template
template = bootstrap_template
# Use the template directly if it's a path to an actual file
- if File.exists?(template)
- Chef::Log.debug("Using the specified bootstrap template: #{File.dirname(template)}")
+ if File.exist?(template)
+ Chef::Log.trace("Using the specified bootstrap template: #{File.dirname(template)}")
return template
end
# 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(__dir__, "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 }
bootstrap_files << Gem.find_files(File.join("chef", "knife", "bootstrap", "#{template}.erb"))
bootstrap_files.flatten!
template_file = Array(bootstrap_files).find do |bootstrap_template|
- Chef::Log.debug("Looking for bootstrap template in #{File.dirname(bootstrap_template)}")
- File.exists?(bootstrap_template)
+ Chef::Log.trace("Looking for bootstrap template in #{File.dirname(bootstrap_template)}")
+ File.exist?(bootstrap_template)
end
unless template_file
@@ -335,7 +506,7 @@ class Chef
raise Errno::ENOENT
end
- Chef::Log.debug("Found bootstrap template in #{File.dirname(template_file)}")
+ Chef::Log.trace("Found bootstrap template: #{template_file}")
template_file
end
@@ -344,13 +515,18 @@ class Chef
@secret ||= encryption_secret_provided_ignore_encrypt_flag? ? read_secret : nil
end
+ # Establish bootstrap context for template rendering.
+ # Requires connection to be a live connection in order to determine
+ # the correct platform.
def bootstrap_context
- @bootstrap_context ||= Knife::Core::BootstrapContext.new(
- config,
- config[:run_list],
- Chef::Config,
- secret
- )
+ @bootstrap_context ||=
+ if connection.windows?
+ require_relative "core/windows_bootstrap_context"
+ Knife::Core::WindowsBootstrapContext.new(config, config[:run_list], Chef::Config, secret)
+ else
+ require_relative "core/bootstrap_context"
+ Knife::Core::BootstrapContext.new(config, config[:run_list], Chef::Config, secret)
+ end
end
def first_boot_attributes
@@ -365,61 +541,218 @@ class Chef
end
def run
- if @config[:first_boot_attributes] && @config[:first_boot_attributes_from_file]
- raise Chef::Exceptions::BootstrapCommandInputError
- end
+ check_license if ChefUtils::Dist::Org::ENFORCE_LICENSE
+ plugin_setup!
validate_name_args!
- validate_options!
+ validate_protocol!
+ validate_first_boot_attributes!
+ validate_winrm_transport_opts!
+ validate_policy_options!
+ plugin_validate_options!
+
+ winrm_warn_no_ssl_verification
+ warn_on_short_session_timeout
+ plugin_create_instance!
$stdout.sync = true
+ connect!
+ register_client
+
+ content = render_template
+ bootstrap_path = upload_bootstrap(content)
+ perform_bootstrap(bootstrap_path)
+ plugin_finalize
+ ensure
+ connection.del_file!(bootstrap_path) if connection && bootstrap_path
+ end
+ def register_client
# chef-vault integration must use the new client-side hawtness, otherwise to use the
# 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])))
+ (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(client_builder.client)
bootstrap_context.client_pem = client_builder.client_path
else
- ui.info("Doing old-style registration with the validation key at #{Chef::Config[:validation_key]}...")
- ui.info("Delete your validation key in order to use your user credentials instead")
- ui.info("")
+ ui.warn "Performing legacy client registration with the validation key at #{Chef::Config[:validation_key]}..."
+ ui.warn "Remove the key file or remove the 'validation_key' configuration option from your config.rb (knife.rb) to use more secure user credentials for client registration."
+ end
+ end
+
+ def perform_bootstrap(remote_bootstrap_script_path)
+ ui.info("Bootstrapping #{ui.color(server_name, :bold)}")
+ cmd = bootstrap_command(remote_bootstrap_script_path)
+ r = connection.run_command(cmd) do |data|
+ ui.msg("#{ui.color(" [#{connection.hostname}]", :cyan)} #{data}")
end
+ if r.exit_status != 0
+ ui.error("The following error occurred on #{server_name}:")
+ ui.error(r.stderr)
+ exit 1
+ end
+ end
- ui.info("Connecting to #{ui.color(server_name, :bold)}")
+ def connect!
+ ui.info("Connecting to #{ui.color(server_name, :bold)} using #{connection_protocol}")
+ opts ||= connection_opts.dup
+ do_connect(opts)
+ rescue Train::Error => e
+ # We handle these by message text only because train only loads the
+ # transports and protocols that it needs - so the exceptions may not be defined,
+ # and we don't want to require files internal to train.
+ if e.message =~ /fingerprint (\S+) is unknown for "(.+)"/ # Train::Transports::SSHFailed
+ fingerprint = $1
+ hostname, ip = $2.split(",")
+ # TODO: convert the SHA256 base64 value to hex with colons
+ # 'ssh' example output:
+ # RSA key fingerprint is e5:cb:c0:e2:21:3b:12:52:f8:ce:cb:00:24:e2:0c:92.
+ # ECDSA key fingerprint is 5d:67:61:08:a9:d7:01:fd:5e:ae:7e:09:40:ef:c0:3c.
+ # will exit 3 on N
+ ui.confirm <<~EOM
+ The authenticity of host '#{hostname} (#{ip})' can't be established.
+ fingerprint is #{fingerprint}.
+
+ Are you sure you want to continue connecting
+ EOM
+ # FIXME: this should save the key to known_hosts but doesn't appear to be
+ config[:ssh_verify_host_key] = :accept_new
+ conn_opts = connection_opts(reset: true)
+ opts.merge! conn_opts
+ retry
+ elsif (ssh? && e.cause && e.cause.class == Net::SSH::AuthenticationFailed) || (ssh? && e.class == Train::ClientError && e.reason == :no_ssh_password_or_key_available)
+ if connection.password_auth?
+ raise
+ else
+ ui.warn("Failed to authenticate #{opts[:user]} to #{server_name} - trying password auth")
+ password = ui.ask("Enter password for #{opts[:user]}@#{server_name}:", echo: false)
+ end
- begin
- knife_ssh.run
- rescue Net::SSH::AuthenticationFailed
- if config[:ssh_password]
+ opts.merge! force_ssh_password_opts(password)
+ retry
+ else
+ raise
+ end
+ rescue RuntimeError => e
+ if winrm? && e.message == "password is a required option"
+ if connection.password_auth?
raise
else
- ui.info("Failed to authenticate #{knife_ssh.config[:ssh_user]} - trying password auth")
- knife_ssh_with_password_auth.run
+ ui.warn("Failed to authenticate #{opts[:user]} to #{server_name} - trying password auth")
+ password = ui.ask("Enter password for #{opts[:user]}@#{server_name}:", echo: false)
end
+
+ opts.merge! force_winrm_password_opts(password)
+ retry
+ else
+ raise
end
end
+ def handle_ssh_error(e); end
+
+ # url values override CLI flags, if you provide both
+ # we'll use the one that you gave in the URL.
+ def connection_protocol
+ return @connection_protocol if @connection_protocol
+
+ from_url = host_descriptor =~ %r{^(.*)://} ? $1 : nil
+ from_knife = config[:connection_protocol]
+ @connection_protocol = from_url || from_knife || "ssh"
+ end
+
+ def do_connect(conn_options)
+ @connection = TrainConnector.new(host_descriptor, connection_protocol, conn_options)
+ connection.connect!
+ rescue Train::UserError => e
+ limit ||= 1
+ if !conn_options.key?(:pty) && e.reason == :sudo_no_tty
+ ui.warn("#{e.message} - trying with pty request")
+ conn_options[:pty] = true # ensure we can talk to systems with requiretty set true in sshd config
+ retry
+ elsif config[:use_sudo_password] && (e.reason == :sudo_password_required || e.reason == :bad_sudo_password) && limit < 3
+ ui.warn("Failed to authenticate #{conn_options[:user]} to #{server_name} - #{e.message} \n sudo: #{limit} incorrect password attempt")
+ sudo_password = ui.ask("Enter sudo password for #{conn_options[:user]}@#{server_name}:", echo: false)
+ limit += 1
+ conn_options[:sudo_password] = sudo_password
+
+ retry
+ else
+ raise
+ end
+ end
+
+ # Fail if both first_boot_attributes and first_boot_attributes_from_file
+ # are set.
+ def validate_first_boot_attributes!
+ if @config[:first_boot_attributes] && @config[:first_boot_attributes_from_file]
+ raise Chef::Exceptions::BootstrapCommandInputError
+ end
+
+ true
+ end
+
+ # FIXME: someone needs to clean this up properly: https://github.com/chef/chef/issues/9645
+ # This code is deliberately left without an abstraction around deprecating the config options to avoid knife plugins from
+ # using those methods (which will need to be deprecated and break them) via inheritance (ruby does not have a true `private`
+ # so the lack of any inheritable implementation is because of that).
+ #
+ def winrm_auth_method
+ config.key?(:winrm_auth_method) ? config[:winrm_auth_method] : config.key?(:winrm_authentications_protocol) ? config[:winrm_authentication_protocol] : "negotiate" # rubocop:disable Style/NestedTernaryOperator
+ end
+
+ def ssh_verify_host_key
+ config.key?(:ssh_verify_host_key) ? config[:ssh_verify_host_key] : config.key?(:host_key_verify) ? config[:host_key_verify] : "always" # rubocop:disable Style/NestedTernaryOperator
+ end
+
+ # Fail if using plaintext auth without ssl because
+ # this can expose keys in plaintext on the wire.
+ # TODO test for this method
+ # TODO check that the protocol is valid.
+ def validate_winrm_transport_opts!
+ return true unless winrm?
+
+ if Chef::Config[:validation_key] && !File.exist?(File.expand_path(Chef::Config[:validation_key]))
+ if winrm_auth_method == "plaintext" &&
+ config[:winrm_ssl] != true
+ ui.error <<~EOM
+ Validatorless bootstrap over unsecure winrm channels could expose your
+ key to network sniffing.
+ Please use a 'winrm_auth_method' other than 'plaintext',
+ or enable ssl on #{server_name} then use the ---winrm-ssl flag
+ to connect.
+ EOM
+
+ exit 1
+ end
+ end
+ true
+ end
+
+ # fail if the server_name is nil
def validate_name_args!
if server_name.nil?
ui.error("Must pass an FQDN or ip to bootstrap")
exit 1
- elsif server_name == "windows"
- # catches "knife bootstrap windows" when that command is not installed
- ui.warn("Hostname containing 'windows' specified. Please install 'knife-windows' if you are attempting to bootstrap a Windows node via WinRM.")
end
end
- def validate_options!
+ # Ensure options are valid by checking policyfile values.
+ #
+ # The method call will cause the program to exit(1) if:
+ # * Only one of --policy-name and --policy-group is specified
+ # * Policyfile options are set and --run-list is set as well
+ #
+ # @return [TrueClass] If options are valid.
+ def validate_policy_options!
if incomplete_policyfile_options?
ui.error("--policy-name and --policy-group must be specified together")
exit 1
@@ -427,45 +760,357 @@ class Chef
ui.error("Policyfile options and --run-list are exclusive")
exit 1
end
+ end
+
+ # Ensure a valid protocol is provided for target host connection
+ #
+ # The method call will cause the program to exit(1) if:
+ # * Conflicting protocols are given via the target URI and the --protocol option
+ # * The protocol is not a supported protocol
+ #
+ # @return [TrueClass] If options are valid.
+ def validate_protocol!
+ from_cli = config[:connection_protocol]
+ if from_cli && connection_protocol != from_cli
+ # Hanging indent to align with the ERROR: prefix
+ ui.error <<~EOM
+ The URL '#{host_descriptor}' indicates protocol is '#{connection_protocol}'
+ while the --protocol flag specifies '#{from_cli}'. Please include
+ only one or the other.
+ EOM
+ exit 1
+ end
+
+ unless SUPPORTED_CONNECTION_PROTOCOLS.include?(connection_protocol)
+ ui.error <<~EOM
+ Unsupported protocol '#{connection_protocol}'.
+
+ Supported protocols are: #{SUPPORTED_CONNECTION_PROTOCOLS.join(" ")}
+ EOM
+ exit 1
+ end
+ true
+ end
+
+ # Validate any additional options
+ #
+ # Plugins that subclass bootstrap, e.g. knife-ec2, can use this method to validate any additional options before any other actions are executed
+ #
+ # @return [TrueClass] If options are valid or exits
+ def plugin_validate_options!
true
end
- def knife_ssh
- ssh = Chef::Knife::Ssh.new
- ssh.ui = ui
- ssh.name_args = [ server_name, ssh_command ]
- 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[: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
- ssh
+ # Create the server that we will bootstrap, if necessary
+ #
+ # Plugins that subclass bootstrap, e.g. knife-ec2, can use this method to call out to an API to build an instance of the server we wish to bootstrap
+ #
+ # @return [TrueClass] If instance successfully created, or exits
+ def plugin_create_instance!
+ true
+ end
+
+ # Perform any setup necessary by the plugin
+ #
+ # Plugins that subclass bootstrap, e.g. knife-ec2, can use this method to create connection objects
+ #
+ # @return [TrueClass] If instance successfully created, or exits
+ def plugin_setup!; end
+
+ # Perform any teardown or cleanup necessary by the plugin
+ #
+ # Plugins that subclass bootstrap, e.g. knife-ec2, can use this method to display a message or perform any cleanup
+ #
+ # @return [void]
+ def plugin_finalize; end
+
+ # If session_timeout is too short, it is likely
+ # a holdover from "--winrm-session-timeout" which used
+ # minutes as its unit, instead of seconds.
+ # Warn the human so that they are not surprised.
+ #
+ def warn_on_short_session_timeout
+ if session_timeout && session_timeout <= 15
+ ui.warn <<~EOM
+ You provided '--session-timeout #{session_timeout}' second(s).
+ Did you mean '--session-timeout #{session_timeout * 60}' seconds?
+ EOM
+ end
+ end
+
+ def winrm_warn_no_ssl_verification
+ return unless winrm?
+
+ # REVIEWER NOTE
+ # The original check from knife plugin did not include winrm_ssl_peer_fingerprint
+ # Reference:
+ # https://github.com/chef/knife-windows/blob/92d151298142be4a4750c5b54bb264f8d5b81b8a/lib/chef/knife/winrm_knife_base.rb#L271-L273
+ # TODO Seems like we should also do a similar warning if ssh_verify_host == false
+ if config[:ca_trust_file].nil? &&
+ config[:winrm_no_verify_cert] &&
+ config[:winrm_ssl_peer_fingerprint].nil?
+ ui.warn <<~WARN
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ SSL validation of HTTPS requests for the WinRM transport is disabled.
+ HTTPS WinRM connections are still encrypted, but knife is not able
+ to detect forged replies or spoofing attacks.
+
+ To work around this issue you can use the flag `--winrm-no-verify-cert`
+ or add an entry like this to your knife configuration file:
+
+ # Verify all WinRM HTTPS connections
+ knife[:winrm_no_verify_cert] = true
+
+ You can also specify a ca_trust_file via --ca-trust-file,
+ or the expected fingerprint of the target host's certificate
+ via --winrm-ssl-peer-fingerprint.
+ WARN
+ end
+ end
+
+ # @return a configuration hash suitable for connecting to the remote
+ # host via train
+ def connection_opts(reset: false)
+ return @connection_opts unless @connection_opts.nil? || reset == true
+
+ @connection_opts = {}
+ @connection_opts.merge! base_opts
+ @connection_opts.merge! host_verify_opts
+ @connection_opts.merge! gateway_opts
+ @connection_opts.merge! sudo_opts
+ @connection_opts.merge! winrm_opts
+ @connection_opts.merge! ssh_opts
+ @connection_opts.merge! ssh_identity_opts
+ @connection_opts
+ end
+
+ def winrm?
+ connection_protocol == "winrm"
+ end
+
+ def ssh?
+ connection_protocol == "ssh"
+ end
+
+ # Common configuration for all protocols
+ def base_opts
+ port = config_for_protocol(:port)
+ user = config_for_protocol(:user)
+ {}.tap do |opts|
+ opts[:logger] = Chef::Log
+ opts[:password] = config[:connection_password] if config.key?(:connection_password)
+ opts[:user] = user if user
+ opts[:max_wait_until_ready] = config[:max_wait].to_f unless config[:max_wait].nil?
+ # TODO - when would we need to provide rdp_port vs port? Or are they not mutually exclusive?
+ opts[:port] = port if port
+ end
+ end
+
+ def host_verify_opts
+ if winrm?
+ { self_signed: config[:winrm_no_verify_cert] === true }
+ elsif ssh?
+ # Fall back to the old knife config key name for back compat.
+ { verify_host_key: ssh_verify_host_key }
+ else
+ {}
+ end
end
- def knife_ssh_with_password_auth
- ssh = knife_ssh
- ssh.config[:ssh_identity_file] = nil
- ssh.config[:ssh_password] = ssh.get_password
- ssh
+ def ssh_opts
+ opts = {}
+ return opts if winrm?
+
+ opts[:non_interactive] = true # Prevent password prompts from underlying net/ssh
+ opts[:forward_agent] = (config[:ssh_forward_agent] === true)
+ opts[:connection_timeout] = session_timeout
+ opts
+ end
+
+ def ssh_identity_opts
+ opts = {}
+ return opts if winrm?
+
+ identity_file = config[:ssh_identity_file]
+ if identity_file
+ opts[:key_files] = [identity_file]
+ # We only set keys_only based on the explicit ssh_identity_file;
+ # someone may use a gateway key and still expect password auth
+ # on the target. Similarly, someone may have a default key specified
+ # in knife config, but have provided a password on the CLI.
+
+ # REVIEW NOTE: this is a new behavior. Originally, ssh_identity_file
+ # could only be populated from CLI options, so there was no need to check
+ # for this. We will also set keys_only to false only if there are keys
+ # and no password.
+ # If both are present, train(via net/ssh) will prefer keys, falling back to password.
+ # Reference: https://github.com/chef/chef/blob/master/lib/chef/knife/ssh.rb#L272
+ opts[:keys_only] = config.key?(:connection_password) == false
+ else
+ opts[:key_files] = []
+ opts[:keys_only] = false
+ end
+
+ gateway_identity_file = config[:ssh_gateway] ? config[:ssh_gateway_identity] : nil
+ unless gateway_identity_file.nil?
+ opts[:key_files] << gateway_identity_file
+ end
+
+ opts
end
- def ssh_command
- command = render_template
+ def gateway_opts
+ opts = {}
+ if config[:ssh_gateway]
+ split = config[:ssh_gateway].split("@", 2)
+ if split.length == 1
+ gw_host = split[0]
+ else
+ gw_user = split[0]
+ gw_host = split[1]
+ end
+ gw_host, gw_port = gw_host.split(":", 2)
+ # TODO - validate convertible port in config validation?
+ gw_port = Integer(gw_port) rescue nil
+ opts[:bastion_host] = gw_host
+ opts[:bastion_user] = gw_user
+ opts[:bastion_port] = gw_port
+ end
+ opts
+ end
+ # use_sudo - tells bootstrap to use the sudo command to run bootstrap
+ # use_sudo_password - tells bootstrap to use the sudo command to run bootstrap
+ # and to use the password specified with --password
+ # TODO: I'd like to make our sudo options sane:
+ # --sudo (bool) - use sudo
+ # --sudo-password PASSWORD (default: :password) - use this password for sudo
+ # --sudo-options "opt,opt,opt" to pass into sudo
+ # --sudo-command COMMAND sudo command other than sudo
+ # REVIEW NOTE: knife bootstrap did not pull sudo values from Chef::Config,
+ # should we change that for consistency?
+ def sudo_opts
+ return {} if winrm?
+
+ opts = { sudo: false }
if config[:use_sudo]
- sudo_prefix = config[:use_sudo_password] ? "echo '#{config[:ssh_password]}' | sudo -S " : "sudo "
- command = config[:preserve_home] ? "#{sudo_prefix} #{command}" : "#{sudo_prefix} -H #{command}"
+ opts[:sudo] = true
+ if config[:use_sudo_password]
+ opts[:sudo_password] = config[:connection_password]
+ end
+ if config[:preserve_home]
+ opts[:sudo_options] = "-H"
+ end
+ end
+ opts
+ end
+
+ def winrm_opts
+ return {} unless winrm?
+
+ opts = {
+ winrm_transport: winrm_auth_method, # winrm gem and train calls auth method 'transport'
+ winrm_basic_auth_only: config[:winrm_basic_auth_only] || false,
+ ssl: config[:winrm_ssl] === true,
+ ssl_peer_fingerprint: config[:winrm_ssl_peer_fingerprint],
+ }
+
+ if winrm_auth_method == "kerberos"
+ opts[:kerberos_service] = config[:kerberos_service] if config[:kerberos_service]
+ opts[:kerberos_realm] = config[:kerberos_realm] if config[:kerberos_service]
+ end
+
+ if config[:ca_trust_file]
+ opts[:ca_trust_path] = config[:ca_trust_file]
+ end
+
+ opts[:operation_timeout] = session_timeout
+
+ opts
+ end
+
+ # Config overrides to force password auth.
+ def force_ssh_password_opts(password)
+ {
+ password: password,
+ non_interactive: false,
+ keys_only: false,
+ key_files: [],
+ auth_methods: %i{password keyboard_interactive},
+ }
+ end
+
+ def force_winrm_password_opts(password)
+ {
+ password: password,
+ }
+ end
+
+ # This is for deprecating config options. The fallback_key can be used
+ # to pull an old knife config option out of the config file when the
+ # cli value has been renamed. This is different from the deprecated
+ # cli values, since these are for config options that have no corresponding
+ # cli value.
+ #
+ # DO NOT USE - this whole API is considered deprecated
+ #
+ # @api deprecated
+ #
+ def config_value(key, fallback_key = nil, default = nil)
+ Chef.deprecated(:knife_bootstrap_apis, "Use of config_value is deprecated. Knife plugin authors should access the config hash directly, which does correct merging of cli and config options.")
+ if config.key?(key)
+ # the first key is the primary key so we check the merged hash first
+ config[key]
+ elsif config.key?(fallback_key)
+ # we get the old config option here (the deprecated cli option shouldn't exist)
+ config[fallback_key]
+ else
+ default
end
+ end
- command
+ def upload_bootstrap(content)
+ script_name = connection.windows? ? "bootstrap.bat" : "bootstrap.sh"
+ remote_path = connection.normalize_path(File.join(connection.temp_dir, script_name))
+ connection.upload_file_content!(content, remote_path)
+ remote_path
+ end
+
+ # build the command string for bootstrapping
+ # @return String
+ def bootstrap_command(remote_path)
+ if connection.windows?
+ "cmd.exe /C #{remote_path}"
+ else
+ "sh #{remote_path}"
+ end
end
private
+ # To avoid cluttering the CLI options, some flags (such as port and user)
+ # are shared between protocols. However, there is still a need to allow the operator
+ # to specify defaults separately, since they may not be the same values for different
+ # protocols.
+
+ # These keys are available in Chef::Config, and are prefixed with the protocol name.
+ # For example, :user CLI option will map to :winrm_user and :ssh_user Chef::Config keys,
+ # based on the connection protocol in use.
+
+ # @api private
+ def config_for_protocol(option)
+ if option == :port
+ config[:connection_port] || config[knife_key_for_protocol(option)]
+ else
+ config[:connection_user] || config[knife_key_for_protocol(option)]
+ end
+ end
+
+ # @api private
+ def knife_key_for_protocol(option)
+ "#{connection_protocol}_#{option}".to_sym
+ end
+
# True if policy_name and run_list are both given
def policyfile_and_run_list_given?
run_list_given? && policyfile_options_given?
@@ -484,6 +1129,14 @@ class Chef
(!!config[:policy_name] ^ config[:policy_group])
end
+ # session_timeout option has a default that may not arrive, particularly if
+ # we're being invoked from a plugin that doesn't merge_config.
+ def session_timeout
+ timeout = config[:session_timeout]
+ return options[:session_timeout][:default] if timeout.nil?
+
+ timeout.to_i
+ 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 9990565856..20759d6fdf 100644
--- a/lib/chef/knife/bootstrap/chef_vault_handler.rb
+++ b/lib/chef/knife/bootstrap/chef_vault_handler.rb
@@ -1,6 +1,6 @@
#
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,15 +15,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-require "chef/knife/bootstrap"
-
class Chef
class Knife
class Bootstrap < Knife
class ChefVaultHandler
# @return [Hash] knife merged config, typically @config
- attr_accessor :knife_config
+ attr_accessor :config
# @return [Chef::Knife::UI] ui object for output
attr_accessor :ui
@@ -31,11 +29,15 @@ class Chef
# @return [Chef::ApiClient] vault client
attr_reader :client
- # @param knife_config [Hash] knife merged config, typically @config
+ # @param config [Hash] knife merged config, typically @config
# @param ui [Chef::Knife::UI] ui object for output
- def initialize(knife_config: {}, ui: nil)
- @knife_config = knife_config
- @ui = ui
+ def initialize(config: {}, knife_config: nil, ui: nil)
+ @config = config
+ unless knife_config.nil?
+ @config = knife_config
+ Chef.deprecated(:knife_bootstrap_apis, "The knife_config option to the Bootstrap::ClientBuilder object is deprecated and has been renamed to just 'config'")
+ end
+ @ui = ui
end
# Updates the chef vault items for the newly created client.
@@ -87,20 +89,20 @@ class Chef
# @return [String] string with serialized JSON representing the chef vault items
def bootstrap_vault_json
- knife_config[:bootstrap_vault_json]
+ config[:bootstrap_vault_json]
end
# @return [String] JSON text in a file representing the chef vault items
def bootstrap_vault_file
- knife_config[:bootstrap_vault_file]
+ config[:bootstrap_vault_file]
end
# @return [Hash] Ruby object representing the chef vault items to create
def bootstrap_vault_item
- knife_config[:bootstrap_vault_item]
+ config[:bootstrap_vault_item]
end
- # Helper to return a ruby object represeting all the data bags and items
+ # Helper to return a ruby object representing all the data bags and items
# to update via chef-vault.
#
# @return [Hash] deserialized ruby hash with all the vault items
@@ -110,7 +112,7 @@ class Chef
if bootstrap_vault_item
bootstrap_vault_item
else
- json = bootstrap_vault_json ? bootstrap_vault_json : File.read(bootstrap_vault_file)
+ json = bootstrap_vault_json || File.read(bootstrap_vault_file)
Chef::JSONCompat.from_json(json)
end
end
@@ -131,7 +133,7 @@ class Chef
#
# @param vault [String] name of the chef-vault encrypted data bag
# @param item [String] name of the chef-vault encrypted item
- # @returns [ChefVault::Item] ChefVault::Item object
+ # @return [ChefVault::Item] ChefVault::Item object
def load_chef_bootstrap_vault_item(vault, item)
ChefVault::Item.load(vault, item)
end
@@ -142,11 +144,12 @@ class Chef
def require_chef_vault!
@require_chef_vault ||=
begin
- error_message = "Knife bootstrap requires version 2.6.0 or higher of the chef-vault gem to configure chef vault items"
+ error_message = "Knife bootstrap requires version 2.6.0 or higher of the chef-vault gem to configure 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 error_message
diff --git a/lib/chef/knife/bootstrap/client_builder.rb b/lib/chef/knife/bootstrap/client_builder.rb
index cab33cd811..d9c3d83d06 100644
--- a/lib/chef/knife/bootstrap/client_builder.rb
+++ b/lib/chef/knife/bootstrap/client_builder.rb
@@ -1,6 +1,6 @@
#
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,12 +16,11 @@
# limitations under the License.
#
-require "chef/node"
-require "chef/server_api"
-require "chef/api_client/registration"
-require "chef/api_client"
-require "chef/knife/bootstrap"
-require "tmpdir"
+require_relative "../../node"
+require_relative "../../server_api"
+require_relative "../../api_client/registration"
+require_relative "../../api_client"
+require "tmpdir" unless defined?(Dir.mktmpdir)
class Chef
class Knife
@@ -29,7 +28,7 @@ class Chef
class ClientBuilder
# @return [Hash] knife merged config, typically @config
- attr_accessor :knife_config
+ attr_accessor :config
# @return [Hash] chef config object
attr_accessor :chef_config
# @return [Chef::Knife::UI] ui object for output
@@ -37,13 +36,17 @@ class Chef
# @return [Chef::ApiClient] client saved on run
attr_reader :client
- # @param knife_config [Hash] Hash of knife config settings
+ # @param config [Hash] Hash of knife config settings
# @param chef_config [Hash] Hash of chef config settings
# @param ui [Chef::Knife::UI] UI object for output
- def initialize(knife_config: {}, chef_config: {}, ui: nil)
- @knife_config = knife_config
- @chef_config = chef_config
- @ui = ui
+ def initialize(config: {}, knife_config: nil, chef_config: {}, ui: nil)
+ @config = config
+ unless knife_config.nil?
+ @config = knife_config
+ Chef.deprecated(:knife_bootstrap_apis, "The knife_config option to the Bootstrap::ClientBuilder object is deprecated and has been renamed to just 'config'")
+ end
+ @chef_config = chef_config
+ @ui = ui
end
# Main entry. Prompt the user to clean up any old client or node objects. Then create
@@ -78,34 +81,34 @@ class Chef
private
- # @return [String] node name from the knife_config
+ # @return [String] node name from the config
def node_name
- knife_config[:chef_node_name]
+ config[:chef_node_name]
end
- # @return [String] enviroment from the knife_config
+ # @return [String] environment from the config
def environment
- knife_config[:environment]
+ config[:environment]
end
- # @return [String] run_list from the knife_config
+ # @return [String] run_list from the config
def run_list
- knife_config[:run_list]
+ config[:run_list]
end
- # @return [String] policy_name from the knife_config
+ # @return [String] policy_name from the config
def policy_name
- knife_config[:policy_name]
+ config[:policy_name]
end
- # @return [String] policy_group from the knife_config
+ # @return [String] policy_group from the config
def policy_group
- knife_config[:policy_group]
+ config[:policy_group]
end
- # @return [Hash,Array] Object representation of json first-boot attributes from the knife_config
+ # @return [Hash,Array] Object representation of json first-boot attributes from the config
def first_boot_attributes
- knife_config[:first_boot_attributes]
+ config[:first_boot_attributes]
end
# @return [String] chef server url from the Chef::Config
@@ -155,7 +158,7 @@ class Chef
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|
+ (config[:tags] || []).each do |tag|
node.tags << tag
end
node
@@ -187,14 +190,15 @@ class Chef
def resource_exists?(relative_path)
rest.get(relative_path)
true
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => e
raise unless e.response.code == "404"
+
false
end
# @return [Chef::ServerAPI] REST client using the client credentials
def client_rest
- @client_rest ||= Chef::ServerAPI.new(chef_server_url, :client_name => node_name, :signing_key_filename => client_path)
+ @client_rest ||= Chef::ServerAPI.new(chef_server_url, client_name: node_name, signing_key_filename: client_path)
end
# @return [Chef::ServerAPI] REST client using the cli user's knife credentials
diff --git a/lib/chef/knife/bootstrap/templates/README.md b/lib/chef/knife/bootstrap/templates/README.md
index b5bca25d8e..7f28f8f40f 100644
--- a/lib/chef/knife/bootstrap/templates/README.md
+++ b/lib/chef/knife/bootstrap/templates/README.md
@@ -5,7 +5,7 @@ standardized on the [Omnibus](https://github.com/chef/omnibus) built installatio
packages.
The 'chef-full' template downloads a script which is used to determine the correct
-Omnibus package for this system from the [Omnitruck](https://docs.chef.io/api_omnitruck.html) API.
+Omnibus package for this system from the [Omnitruck](https://docs.chef.io/api_omnitruck/) 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](https://docs.chef.io/knife_bootstrap.html#custom-templates).
+needs are unique. Additional information can be found on the [docs site](https://docs.chef.io/knife_bootstrap/#custom-templates).
diff --git a/lib/chef/knife/bootstrap/templates/chef-full.erb b/lib/chef/knife/bootstrap/templates/chef-full.erb
index 6007ff9859..2e0c80eaef 100644
--- a/lib/chef/knife/bootstrap/templates/chef-full.erb
+++ b/lib/chef/knife/bootstrap/templates/chef-full.erb
@@ -1,5 +1,5 @@
-sh -c '
-<%= "export https_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%>
+<%= "https_proxy=\"#{@config[:bootstrap_proxy]}\" export https_proxy" if @config[:bootstrap_proxy] %>
+<%= "no_proxy=\"#{@config[:bootstrap_no_proxy]}\" export no_proxy" if @config[:bootstrap_no_proxy] %>
if test "x$TMPDIR" = "x"; then
tmp="/tmp"
@@ -37,7 +37,7 @@ capture_tmp_stderr() {
# 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
+ wget <%= "--proxy=on " if @config[:bootstrap_proxy] %> <%= @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
@@ -57,7 +57,7 @@ do_wget() {
# 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
+ curl -sL <%= "--proxy \"#{@config[:bootstrap_proxy]}\" " if @config[:bootstrap_proxy] %> <%= @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
@@ -162,16 +162,22 @@ do_download() {
return 16
}
-<% if knife_config[:bootstrap_install_command] %>
- <%= knife_config[:bootstrap_install_command] %>
+<%# Run any custom commands before installing chef-client -%>
+<%# Ex. wait for cloud-init to complete -%>
+<% if @config[:bootstrap_preinstall_command] %>
+ <%= @config[:bootstrap_preinstall_command] %>
+<% end %>
+
+<% if @config[:bootstrap_install_command] %>
+ <%= @config[:bootstrap_install_command] %>
<% else %>
- 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"
+ install_sh="<%= @config[:bootstrap_url] ? @config[:bootstrap_url] : "https://omnitruck.chef.io/chef/install.sh" %>"
+ if test -f /usr/bin/<%= ChefUtils::Dist::Infra::CLIENT %>; then
+ echo "-----> Existing <%= ChefUtils::Dist::Infra::PRODUCT %> installation detected"
else
- echo "-----> Installing Chef Omnibus (<%= latest_current_chef_version_string %>)"
+ echo "-----> Installing Chef Omnibus (<%= @config[:channel] %>/<%= version_to_install %>)"
do_download ${install_sh} $tmp_dir/install.sh
- sh $tmp_dir/install.sh -P chef <%= latest_current_chef_version_string %>
+ sh $tmp_dir/install.sh -P <%= @config[:bootstrap_product] %> -c <%= @config[:channel] %> -v <%= version_to_install %>
fi
<% end %>
@@ -182,24 +188,24 @@ fi
mkdir -p /etc/chef
<% if client_pem -%>
-cat > /etc/chef/client.pem <<EOP
+(umask 077 && (cat > /etc/chef/client.pem <<'EOP'
<%= ::File.read(::File.expand_path(client_pem)) %>
EOP
-chmod 0600 /etc/chef/client.pem
+)) || exit 1
<% end -%>
<% if validation_key -%>
-cat > /etc/chef/validation.pem <<EOP
+(umask 077 && (cat > /etc/chef/validation.pem <<'EOP'
<%= validation_key %>
EOP
-chmod 0600 /etc/chef/validation.pem
+)) || exit 1
<% end -%>
<% if encrypted_data_bag_secret -%>
-cat > /etc/chef/encrypted_data_bag_secret <<EOP
+(umask 077 && (cat > /etc/chef/encrypted_data_bag_secret <<'EOP'
<%= encrypted_data_bag_secret %>
EOP
-chmod 0600 /etc/chef/encrypted_data_bag_secret
+)) || exit 1
<% end -%>
<% unless trusted_certs.empty? -%>
@@ -208,21 +214,21 @@ mkdir -p /etc/chef/trusted_certs
<% end -%>
<%# Generate Ohai Hints -%>
-<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%>
+<% unless @config[:hints].nil? || @config[:hints].empty? -%>
mkdir -p /etc/chef/ohai/hints
-<% @chef_config[:knife][:hints].each do |name, hash| -%>
-cat > /etc/chef/ohai/hints/<%= name %>.json <<EOP
+<% @config[: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
+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
@@ -231,6 +237,6 @@ mkdir -p /etc/chef/client.d
<%= client_d %>
<% end -%>
-echo "Starting the first Chef Client run..."
+echo "Starting the first <%= ChefUtils::Dist::Infra::PRODUCT %> Client run..."
-<%= start_chef %>'
+<%= start_chef %>
diff --git a/lib/chef/knife/bootstrap/templates/windows-chef-client-msi.erb b/lib/chef/knife/bootstrap/templates/windows-chef-client-msi.erb
new file mode 100644
index 0000000000..7aa7be49f8
--- /dev/null
+++ b/lib/chef/knife/bootstrap/templates/windows-chef-client-msi.erb
@@ -0,0 +1,278 @@
+@rem
+@rem Author:: Seth Chisamore (<schisamo@chef.io>)
+@rem Copyright:: Copyright (c) 2011-2019 Chef Software, Inc.
+@rem License:: Apache License, Version 2.0
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem http://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@rem Use delayed environment expansion so that ERRORLEVEL can be evaluated with the
+@rem !ERRORLEVEL! syntax which evaluates at execution of the line of script, not when
+@rem the line is read. See help for the /E switch from cmd.exe /? .
+@setlocal ENABLEDELAYEDEXPANSION
+
+<%= "SETX HTTP_PROXY \"#{@config[:bootstrap_proxy]}\"" if @config[:bootstrap_proxy] %>
+
+@set BOOTSTRAP_DIRECTORY=<%= bootstrap_directory %>
+@echo Checking for existing directory "%BOOTSTRAP_DIRECTORY%"...
+@if NOT EXIST %BOOTSTRAP_DIRECTORY% (
+ @echo Existing directory not found, creating.
+ @mkdir %BOOTSTRAP_DIRECTORY%
+) else (
+ @echo Existing directory found, skipping creation.
+)
+
+> <%= bootstrap_directory %>\wget.vbs (
+ <%= win_wget %>
+)
+
+> <%= bootstrap_directory %>\wget.ps1 (
+ <%= win_wget_ps %>
+)
+
+@rem Determine the version and the architecture
+
+@FOR /F "usebackq tokens=1-8 delims=.[] " %%A IN (`ver`) DO (
+@set WinMajor=%%D
+@set WinMinor=%%E
+@set WinBuild=%%F
+)
+
+@echo Detected Windows Version %WinMajor%.%WinMinor% Build %WinBuild%
+
+@set LATEST_OS_VERSION_MAJOR=10
+@set LATEST_OS_VERSION_MINOR=1
+
+@if /i %WinMajor% GTR %LATEST_OS_VERSION_MAJOR% goto VersionUnknown
+@if /i %WinMajor% EQU %LATEST_OS_VERSION_MAJOR% (
+ @if /i %WinMinor% GTR %LATEST_OS_VERSION_MINOR% goto VersionUnknown
+)
+
+goto Version%WinMajor%.%WinMinor%
+
+:VersionUnknown
+@rem If this is an unknown version of windows set the default
+@set MACHINE_OS=2012r2
+@echo Warning: Unknown version of Windows, assuming default of Windows %MACHINE_OS%
+goto architecture_select
+
+:Version6.0
+@set MACHINE_OS=2008
+goto architecture_select
+
+:Version6.1
+@set MACHINE_OS=2008r2
+goto architecture_select
+
+:Version6.2
+@set MACHINE_OS=2012
+goto architecture_select
+
+@rem Currently Windows Server 2012 R2 is treated as equivalent to Windows Server 2012
+:Version6.3
+@set MACHINE_OS=2012r2
+goto architecture_select
+
+:Version10.0
+@set MACHINE_OS=2016
+goto architecture_select
+
+@rem Currently Windows Server 2019 is treated as equivalent to Windows Server 2016
+:Version10.1
+goto Version10.0
+
+:architecture_select
+<% if @config[:architecture] %>
+ @set MACHINE_ARCH=<%= @config[:architecture] %>
+
+ <% if @config[:architecture] == "x86_64" %>
+ IF "%PROCESSOR_ARCHITECTURE%"=="x86" IF not defined PROCESSOR_ARCHITEW6432 (
+ echo You specified bootstrap_architecture as x86_64 but the target machine is i386. A 64 bit program cannot run on a 32 bit machine. > "&2"
+ echo Exiting without bootstrapping. > "&2"
+ exit /b 1
+ )
+ <% end %>
+<% else %>
+ @set MACHINE_ARCH=x86_64
+ IF "%PROCESSOR_ARCHITECTURE%"=="x86" IF not defined PROCESSOR_ARCHITEW6432 @set MACHINE_ARCH=i686
+<% end %>
+goto chef_installed
+
+:chef_installed
+@echo Checking for existing <%= ChefUtils::Dist::Infra::PRODUCT %> installation
+WHERE <%= ChefUtils::Dist::Infra::CLIENT %> >nul 2>nul
+If !ERRORLEVEL!==0 (
+ @echo Existing <%= ChefUtils::Dist::Infra::PRODUCT %> installation detected, skipping download
+ goto key_create
+) else (
+ @echo No existing installation of <%= ChefUtils::Dist::Infra::PRODUCT %> detected
+ goto install
+)
+
+:install
+@rem If user has provided the custom installation command, execute it
+<% if @config[:bootstrap_install_command] %>
+ <%= @config[:bootstrap_install_command] %>
+<% else %>
+ @rem Install Chef using the MSI installer
+
+ @set "LOCAL_DESTINATION_MSI_PATH=<%= local_download_path %>"
+ @set "CHEF_CLIENT_MSI_LOG_PATH=%TEMP%\<%= ChefUtils::Dist::Infra::CLIENT %>-msi%RANDOM%.log"
+
+ @rem Clear any pre-existing downloads
+ @echo Checking for existing downloaded package at "%LOCAL_DESTINATION_MSI_PATH%"
+ @if EXIST "%LOCAL_DESTINATION_MSI_PATH%" (
+ @echo Found existing downloaded package, deleting.
+ @del /f /q "%LOCAL_DESTINATION_MSI_PATH%"
+ @if ERRORLEVEL 1 (
+ echo Warning: Failed to delete pre-existing package with status code !ERRORLEVEL! > "&2"
+ )
+ ) else (
+ echo No existing downloaded packages to delete.
+ )
+
+ @rem If there is somehow a name collision, remove pre-existing log
+ @if EXIST "%CHEF_CLIENT_MSI_LOG_PATH%" del /f /q "%CHEF_CLIENT_MSI_LOG_PATH%"
+
+ @echo Attempting to download client package using PowerShell if available...
+ @set "REMOTE_SOURCE_MSI_URL=<%= msi_url('%MACHINE_OS%', '%MACHINE_ARCH%', 'PowerShell') %>"
+ @set powershell_download=powershell.exe -ExecutionPolicy Unrestricted -InputFormat None -NoProfile -NonInteractive -File <%= bootstrap_directory %>\wget.ps1 "%REMOTE_SOURCE_MSI_URL%" "%LOCAL_DESTINATION_MSI_PATH%"
+ @echo !powershell_download!
+ @call !powershell_download!
+
+ @set DOWNLOAD_ERROR_STATUS=!ERRORLEVEL!
+
+ @if ERRORLEVEL 1 (
+ @echo Failed PowerShell download with status code !DOWNLOAD_ERROR_STATUS! > "&2"
+ @if !DOWNLOAD_ERROR_STATUS!==0 set DOWNLOAD_ERROR_STATUS=2
+ ) else (
+ @rem Sometimes the error level is not set even when the download failed,
+ @rem so check for the file to be sure it is there -- if it is not, we will retry
+ @if NOT EXIST "%LOCAL_DESTINATION_MSI_PATH%" (
+ echo Failed download: download completed, but downloaded file not found > "&2"
+ set DOWNLOAD_ERROR_STATUS=2
+ ) else (
+ echo Download via PowerShell succeeded.
+ )
+ )
+
+ @if NOT %DOWNLOAD_ERROR_STATUS%==0 (
+ @echo Warning: Failed to download "%REMOTE_SOURCE_MSI_URL%" to "%LOCAL_DESTINATION_MSI_PATH%"
+ @echo Warning: Retrying download with cscript ...
+
+ @if EXIST "%LOCAL_DESTINATION_MSI_PATH%" del /f /q "%LOCAL_DESTINATION_MSI_PATH%"
+
+ @set "REMOTE_SOURCE_MSI_URL=<%= msi_url('%MACHINE_OS%', '%MACHINE_ARCH%') %>"
+ cscript /nologo <%= bootstrap_directory %>\wget.vbs /url:"%REMOTE_SOURCE_MSI_URL%" /path:"%LOCAL_DESTINATION_MSI_PATH%"
+
+ @if NOT ERRORLEVEL 1 (
+ @rem Sometimes the error level is not set even when the download failed,
+ @rem so check for the file to be sure it is there.
+ @if NOT EXIST "%LOCAL_DESTINATION_MSI_PATH%" (
+ echo Failed download: download completed, but downloaded file not found > "&2"
+ echo Exiting without bootstrapping due to download failure. > "&2"
+ exit /b 1
+ ) else (
+ echo Download via cscript succeeded.
+ )
+ ) else (
+ echo Failed to download "%REMOTE_SOURCE_MSI_URL%" with status code !ERRORLEVEL!. > "&2"
+ echo Exiting without bootstrapping due to download failure. > "&2"
+ exit /b 1
+ )
+ )
+
+ @echo Installing downloaded client package...
+
+ <%= install_chef %>
+
+ @if ERRORLEVEL 1 (
+ echo <%= ChefUtils::Dist::Infra::CLIENT %> package failed to install with status code !ERRORLEVEL!. > "&2"
+ echo See installation log for additional detail: %CHEF_CLIENT_MSI_LOG_PATH%. > "&2"
+ ) else (
+ @echo Installation completed successfully
+ del /f /q "%CHEF_CLIENT_MSI_LOG_PATH%"
+ )
+
+<% end %>
+
+@rem This line is required to separate the key_create label from the "block boundary"
+@rem Removing these lines will cause the error "The system cannot find the batch label specified - key_create"
+:key_create
+@endlocal
+
+@echo off
+
+<% if client_pem -%>
+> <%= bootstrap_directory %>\client.pem (
+ <%= escape_and_echo(::File.read(::File.expand_path(client_pem))) %>
+)
+<% end -%>
+
+echo Writing validation key...
+
+<% if validation_key -%>
+> <%= bootstrap_directory %>\validation.pem (
+ <%= escape_and_echo(validation_key) %>
+)
+<% end -%>
+
+echo Validation key written.
+@echo on
+
+<% if secret -%>
+> <%= bootstrap_directory %>\encrypted_data_bag_secret (
+ <%= encrypted_data_bag_secret %>
+)
+<% end -%>
+
+<% unless trusted_certs_script.empty? -%>
+ @if NOT EXIST <%= bootstrap_directory %>\trusted_certs (
+ mkdir <%= bootstrap_directory %>\trusted_certs
+ )
+ )
+
+<%= trusted_certs_script %>
+<% end -%>
+
+<%# Generate Ohai Hints -%>
+<% unless @config[:hints].nil? || @config[:hints].empty? -%>
+ @if NOT EXIST <%= bootstrap_directory %>\ohai\hints (
+ mkdir <%= bootstrap_directory %>\ohai\hints
+ )
+
+<% @config[:hints].each do |name, hash| -%>
+> <%= bootstrap_directory %>\ohai\hints\<%= name %>.json (
+ <%= escape_and_echo(hash.to_json) %>
+)
+<% end -%>
+<% end -%>
+
+> <%= bootstrap_directory %>\client.rb (
+ <%= config_content %>
+)
+
+> <%= bootstrap_directory %>\first-boot.json (
+ <%= first_boot %>
+)
+
+<% unless client_d.empty? -%>
+ @if NOT EXIST <%= bootstrap_directory %>\client.d (
+ mkdir <%= bootstrap_directory %>\client.d
+ )
+
+ <%= client_d %>
+<% end -%>
+
+@echo Starting <%= ChefUtils::Dist::Infra::CLIENT %> to bootstrap the node...
+<%= start_chef %>
diff --git a/lib/chef/knife/bootstrap/train_connector.rb b/lib/chef/knife/bootstrap/train_connector.rb
new file mode 100644
index 0000000000..a220ece5bc
--- /dev/null
+++ b/lib/chef/knife/bootstrap/train_connector.rb
@@ -0,0 +1,336 @@
+# Copyright:: Copyright (c) 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 "train"
+require "tempfile" unless defined?(Tempfile)
+require "uri" unless defined?(URI)
+require "securerandom" unless defined?(SecureRandom)
+
+class Chef
+ class Knife
+ class Bootstrap < Knife
+ class TrainConnector
+ SSH_CONFIG_OVERRIDE_KEYS ||= %i{user port proxy}.freeze
+
+ MKTEMP_WIN_COMMAND ||= <<~EOM.freeze
+ $parent = [System.IO.Path]::GetTempPath();
+ [string] $name = [System.Guid]::NewGuid();
+ $tmp = New-Item -ItemType Directory -Path (Join-Path $parent $name);
+ $tmp.FullName
+ EOM
+
+ DEFAULT_REMOTE_TEMP ||= "/tmp".freeze
+
+ def initialize(host_url, default_protocol, opts)
+ @host_url = host_url
+ @default_protocol = default_protocol
+ @opts_in = opts
+ end
+
+ def config
+ @config ||= begin
+ uri_opts = opts_from_uri(@host_url, @default_protocol)
+ transport_config(@host_url, @opts_in.merge(uri_opts))
+ end
+ end
+
+ def connection
+ @connection ||= begin
+ Train.validate_backend(config)
+ train = Train.create(config[:backend], config)
+ # Note that the train connection is not currently connected
+ # to the remote host, but it's ready to go.
+ train.connection
+ end
+ end
+
+ #
+ # Establish a connection to the configured host.
+ #
+ # @raise [TrainError]
+ # @raise [TrainUserError]
+ #
+ # @return [TrueClass] true if the connection could be established.
+ def connect!
+ # Force connection to establish
+ connection.wait_until_ready
+ true
+ end
+
+ #
+ # @return [String] the configured hostname
+ def hostname
+ config[:host]
+ end
+
+ # Answers the question, "is this connection configured for password auth?"
+ # @return [Boolean] true if the connection is configured with password auth
+ def password_auth?
+ config.key? :password
+ end
+
+ # Answers the question, "Am I connected to a linux host?"
+ #
+ # @return [Boolean] true if the connected host is linux.
+ def linux?
+ connection.platform.linux?
+ end
+
+ # Answers the question, "Am I connected to a unix host?"
+ #
+ # @note this will always return true for a linux host
+ # because train classifies linux as a unix
+ #
+ # @return [Boolean] true if the connected host is unix or linux
+ def unix?
+ connection.platform.unix?
+ end
+
+ #
+ # Answers the question, "Am I connected to a Windows host?"
+ #
+ # @return [Boolean] true if the connected host is Windows
+ def windows?
+ connection.platform.windows?
+ end
+
+ #
+ # Creates a temporary directory on the remote host if it
+ # hasn't already. Caches directory location. For *nix,
+ # it will ensure that the directory is owned by the logged-in user
+ #
+ # @return [String] the temporary path created on the remote host.
+ def temp_dir
+ @tmpdir ||= begin
+ if windows?
+ run_command!(MKTEMP_WIN_COMMAND).stdout.split.last
+ else
+ # Get a 6 chars string using secure random
+ # eg. /tmp/chef_XXXXXX.
+ # Use mkdir to create TEMP dir to get rid of mktemp
+ dir = "#{DEFAULT_REMOTE_TEMP}/chef_#{SecureRandom.alphanumeric(6)}"
+ run_command!("mkdir -p '#{dir}'")
+ # Ensure that dir has the correct owner. We are possibly
+ # running with sudo right now - so this directory would be owned by root.
+ # File upload is performed over SCP as the current logged-in user,
+ # so we'll set ownership to ensure that works.
+ run_command!("chown #{config[:user]} '#{dir}'") if config[:sudo]
+
+ dir
+ end
+ end
+ end
+
+ #
+ # Uploads a file from "local_path" to "remote_path"
+ #
+ # @param local_path [String] The path to a file on the local file system
+ # @param remote_path [String] The destination path on the remote file system.
+ # @return NilClass
+ def upload_file!(local_path, remote_path)
+ connection.upload(local_path, remote_path)
+ nil
+ end
+
+ #
+ # Uploads the provided content into the file "remote_path" on the remote host.
+ #
+ # @param content [String] The content to upload into remote_path
+ # @param remote_path [String] The destination path on the remote file system.
+ # @return NilClass
+ def upload_file_content!(content, remote_path)
+ t = Tempfile.new("chef-content")
+ t.binmode
+ t << content
+ t.close
+ upload_file!(t.path, remote_path)
+ nil
+ ensure
+ t.close
+ t.unlink
+ end
+
+ #
+ # Force-deletes the file at "path" from the remote host.
+ #
+ # @param path [String] The path of the file on the remote host
+ def del_file!(path)
+ if windows?
+ run_command!("If (Test-Path \"#{path}\") { Remove-Item -Force -Path \"#{path}\" }")
+ else
+ run_command!("rm -f \"#{path}\"")
+ end
+ nil
+ end
+
+ #
+ # normalizes path across OS's - always use forward slashes, which
+ # Windows and *nix understand.
+ #
+ # @param path [String] The path to normalize
+ #
+ # @return [String] the normalized path
+ def normalize_path(path)
+ path.tr("\\", "/")
+ end
+
+ #
+ # Runs a command on the remote host.
+ #
+ # @param command [String] The command to run.
+ # @param data_handler [Proc] An optional block. When provided, inbound data will be
+ # published via `data_handler.call(data)`. This can allow
+ # callers to receive and render updates from remote command execution.
+ #
+ # @return [Train::Extras::CommandResult] an object containing stdout, stderr, and exit_status
+ def run_command(command, &data_handler)
+ connection.run_command(command, &data_handler)
+ end
+
+ #
+ # Runs a command the remote host
+ #
+ # @param command [String] The command to run.
+ # @param data_handler [Proc] An optional block. When provided, inbound data will be
+ # published via `data_handler.call(data)`. This can allow
+ # callers to receive and render updates from remote command execution.
+ #
+ # @raise Chef::Knife::Bootstrap::RemoteExecutionFailed if an error occurs (non-zero exit status)
+ # @return [Train::Extras::CommandResult] an object containing stdout, stderr, and exit_status
+ def run_command!(command, &data_handler)
+ result = run_command(command, &data_handler)
+ if result.exit_status != 0
+ raise RemoteExecutionFailed.new(hostname, command, result)
+ end
+
+ result
+ end
+
+ private
+
+ # For a given url and set of options, create a config
+ # hash suitable for passing into train.
+ def transport_config(host_url, opts_in)
+ # These baseline opts are not protocol-specific
+ opts = { target: host_url,
+ www_form_encoded_password: true,
+ transport_retries: 2,
+ transport_retry_sleep: 1,
+ backend: opts_in[:backend],
+ logger: opts_in[:logger] }
+
+ # Accepts options provided by caller if they're not already configured,
+ # but note that they will be constrained to valid options for the backend protocol
+ opts.merge!(opts_from_caller(opts, opts_in))
+
+ # WinRM has some additional computed options
+ opts.merge!(opts_inferred_from_winrm(opts, opts_in))
+
+ # Now that everything is populated, fill in anything missing
+ # that may be found in user ssh config
+ opts.merge!(missing_opts_from_ssh_config(opts, opts_in))
+
+ Train.target_config(opts)
+ end
+
+ # Some winrm options are inferred based on other options.
+ # Return a hash of winrm options based on configuration already built.
+ def opts_inferred_from_winrm(config, opts_in)
+ return {} unless config[:backend] == "winrm"
+
+ opts_out = {}
+
+ if opts_in[:ssl]
+ opts_out[:ssl] = true
+ opts_out[:self_signed] = opts_in[:self_signed] || false
+ end
+
+ # See note here: https://github.com/mwrock/WinRM#example
+ if %w{ssl plaintext}.include?(opts_in[:winrm_auth_method])
+ opts_out[:winrm_disable_sspi] = true
+ end
+ opts_out
+ end
+
+ # Returns a hash containing valid options for the current
+ # transport protocol that are not already present in config
+ def opts_from_caller(config, opts_in)
+ # Train.options gives us the supported config options for the
+ # backend provider (ssh, winrm). We'll use that
+ # to filter out options that don't belong
+ # to the transport type we're using.
+ valid_opts = Train.options(config[:backend])
+ opts_in.select do |key, _v|
+ valid_opts.key?(key) && !config.key?(key)
+ end
+ end
+
+ # Extract any of username/password/host/port/transport
+ # that are in the URI and return them as a config has
+ def opts_from_uri(uri, default_protocol)
+ # Train.unpack_target_from_uri only works for complete URIs in
+ # form of proto://[user[:pass]@]host[:port]/
+ # So we'll add the protocol prefix if it's not supplied.
+ uri_to_check = if URI::DEFAULT_PARSER.make_regexp.match(uri)
+ uri
+ else
+ "#{default_protocol}://#{uri}"
+ end
+
+ Train.unpack_target_from_uri(uri_to_check)
+ end
+
+ # This returns a hash that consists of settings
+ # populated from SSH configuration that are not already present
+ # in the configuration passed in.
+ # This is necessary because train will default these values
+ # itself - causing SSH config data to be ignored
+ def missing_opts_from_ssh_config(config, opts_in)
+ return {} unless config[:backend] == "ssh"
+
+ host_cfg = ssh_config_for_host(config[:host])
+ opts_out = {}
+ opts_in.each do |key, _value|
+ if SSH_CONFIG_OVERRIDE_KEYS.include?(key) && !config.key?(key)
+ opts_out[key] = host_cfg[key]
+ end
+ end
+ opts_out
+ end
+
+ # Having this as a method makes it easier to mock
+ # SSH Config for testing.
+ def ssh_config_for_host(host)
+ require "net/ssh" unless defined?(Net::SSH)
+ Net::SSH::Config.for(host)
+ end
+ end
+
+ class RemoteExecutionFailed < StandardError
+ attr_reader :exit_status, :command, :hostname, :stdout, :stderr
+
+ def initialize(hostname, command, result)
+ @hostname = hostname
+ @exit_status = result.exit_status
+ @stderr = result.stderr
+ @stdout = result.stdout
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/knife/client_bulk_delete.rb b/lib/chef/knife/client_bulk_delete.rb
index a7fa7142c8..38d25583b3 100644
--- a/lib/chef/knife/client_bulk_delete.rb
+++ b/lib/chef/knife/client_bulk_delete.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,21 +16,20 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class ClientBulkDelete < Knife
deps do
- require "chef/api_client_v1"
- require "chef/json_compat"
+ require_relative "../api_client_v1"
end
option :delete_validators,
- :short => "-D",
- :long => "--delete-validators",
- :description => "Force deletion of clients if they're validators"
+ short: "-D",
+ long: "--delete-validators",
+ description: "Force deletion of clients if they're validators."
banner "knife client bulk delete REGEX (options)"
@@ -45,7 +44,8 @@ class Chef
clients_to_delete = {}
validators_to_delete = {}
all_clients.each do |name, client|
- next unless name =~ matcher
+ next unless name&.match?(matcher)
+
if client.validator
validators_to_delete[client.name] = client
else
diff --git a/lib/chef/knife/client_create.rb b/lib/chef/knife/client_create.rb
index e28378cd4a..d6e0eab63b 100644
--- a/lib/chef/knife/client_create.rb
+++ b/lib/chef/knife/client_create.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,43 +16,37 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Knife
class ClientCreate < Knife
deps do
- require "chef/api_client_v1"
- require "chef/json_compat"
+ require_relative "../api_client_v1"
end
option :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 => "Open Source Chef Server 11 only. Create the client as an admin.",
- :boolean => true
+ short: "-f FILE",
+ long: "--file FILE",
+ description: "Write the private key to a file if the #{ChefUtils::Dist::Server::PRODUCT} generated one."
option :validator,
- :long => "--validator",
- :description => "Create the client as a validator.",
- :boolean => true
+ long: "--validator",
+ description: "Create the client as a validator.",
+ boolean: true
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)."
+ 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 (Chef Server 12.1+) only. Prevent server from generating a default key pair for you. Cannot be passed with --public-key.",
- :boolean => true
+ short: "-k",
+ long: "--prevent-keygen",
+ description: "Prevent #{ChefUtils::Dist::Server::PRODUCT} from generating a default key pair for you. Cannot be passed with --public-key.",
+ boolean: true
banner "knife client create CLIENTNAME (options)"
@@ -79,10 +73,6 @@ class Chef
client.create_key(true)
end
- if config[:admin]
- client.admin(true)
- end
-
if config[:validator]
client.validator(true)
end
diff --git a/lib/chef/knife/client_delete.rb b/lib/chef/knife/client_delete.rb
index 08cdf6c7dd..3ecfa38242 100644
--- a/lib/chef/knife/client_delete.rb
+++ b/lib/chef/knife/client_delete.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,45 +16,47 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class ClientDelete < Knife
deps do
- require "chef/api_client_v1"
- require "chef/json_compat"
+ require_relative "../api_client_v1"
end
option :delete_validators,
- :short => "-D",
- :long => "--delete-validators",
- :description => "Force deletion of client if it's a validator"
+ short: "-D",
+ long: "--delete-validators",
+ description: "Force deletion of client if it's a validator."
- banner "knife client delete CLIENT (options)"
+ banner "knife client delete [CLIENT [CLIENT]] (options)"
def run
- @client_name = @name_args[0]
-
- if @client_name.nil?
+ if @name_args.length == 0
show_usage
- ui.fatal("You must specify a client name")
+ ui.fatal("You must specify at least one client name")
exit 1
end
- delete_object(Chef::ApiClientV1, @client_name, "client") do
- object = Chef::ApiClientV1.load(@client_name)
+ @name_args.each do |client_name|
+ delete_client(client_name)
+ end
+ end
+
+ def delete_client(client_name)
+ delete_object(Chef::ApiClientV1, client_name, "client") do
+ 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}")
+ ui.fatal("You must specify --delete-validators to delete the validator client #{client_name}")
exit 2
end
end
object.destroy
end
end
-
end
end
end
diff --git a/lib/chef/knife/client_edit.rb b/lib/chef/knife/client_edit.rb
index 948d43cc67..f89f5e38ec 100644
--- a/lib/chef/knife/client_edit.rb
+++ b/lib/chef/knife/client_edit.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,15 +16,14 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class ClientEdit < Knife
deps do
- require "chef/api_client_v1"
- require "chef/json_compat"
+ require_relative "../api_client_v1"
end
banner "knife client edit CLIENT (options)"
@@ -38,7 +37,7 @@ class Chef
exit 1
end
- original_data = Chef::ApiClientV1.load(@client_name).to_hash
+ original_data = Chef::ApiClientV1.load(@client_name).to_h
edited_client = edit_hash(original_data)
if original_data != edited_client
client = Chef::ApiClientV1.from_hash(edited_client)
diff --git a/lib/chef/knife/client_key_create.rb b/lib/chef/knife/client_key_create.rb
index 68ad4d16d2..192d724473 100644
--- a/lib/chef/knife/client_key_create.rb
+++ b/lib/chef/knife/client_key_create.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Cloke (tyler@chef.io)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc
+# Copyright:: Copyright (c) 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/knife"
-require "chef/knife/key_create_base"
+require_relative "../knife"
+require_relative "key_create_base"
class Chef
class Knife
@@ -30,6 +30,12 @@ class Chef
class ClientKeyCreate < Knife
include Chef::Knife::KeyCreateBase
+ banner "knife client key create CLIENT (options)"
+
+ deps do
+ require_relative "key_create"
+ end
+
attr_reader :actor
def initialize(argv = [])
diff --git a/lib/chef/knife/client_key_delete.rb b/lib/chef/knife/client_key_delete.rb
index 64eae2e27c..2d486ffcbd 100644
--- a/lib/chef/knife/client_key_delete.rb
+++ b/lib/chef/knife/client_key_delete.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Cloke (tyler@chef.io)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc
+# Copyright:: Copyright (c) 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"
+require_relative "../knife"
class Chef
class Knife
@@ -29,6 +29,10 @@ class Chef
class ClientKeyDelete < Knife
banner "knife client key delete CLIENT KEYNAME (options)"
+ deps do
+ require_relative "key_delete"
+ end
+
attr_reader :actor
def initialize(argv = [])
diff --git a/lib/chef/knife/client_key_edit.rb b/lib/chef/knife/client_key_edit.rb
index 1dbd3c487b..d178aafc17 100644
--- a/lib/chef/knife/client_key_edit.rb
+++ b/lib/chef/knife/client_key_edit.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Cloke (tyler@chef.io)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc
+# Copyright:: Copyright (c) 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/knife"
-require "chef/knife/key_edit_base"
+require_relative "../knife"
+require_relative "key_edit_base"
class Chef
class Knife
@@ -32,6 +32,10 @@ class Chef
banner "knife client key edit CLIENT KEYNAME (options)"
+ deps do
+ require_relative "key_edit"
+ end
+
attr_reader :actor
def initialize(argv = [])
diff --git a/lib/chef/knife/client_key_list.rb b/lib/chef/knife/client_key_list.rb
index 194ad42931..afc04335d9 100644
--- a/lib/chef/knife/client_key_list.rb
+++ b/lib/chef/knife/client_key_list.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Cloke (tyler@chef.io)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc
+# Copyright:: Copyright (c) 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/knife"
-require "chef/knife/key_list_base"
+require_relative "../knife"
+require_relative "key_list_base"
class Chef
class Knife
@@ -32,6 +32,10 @@ class Chef
banner "knife client key list CLIENT (options)"
+ deps do
+ require_relative "key_list"
+ end
+
attr_reader :actor
def initialize(argv = [])
diff --git a/lib/chef/knife/client_key_show.rb b/lib/chef/knife/client_key_show.rb
index 77f9e96c5a..14e1f0ca7a 100644
--- a/lib/chef/knife/client_key_show.rb
+++ b/lib/chef/knife/client_key_show.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Cloke (tyler@chef.io)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc
+# Copyright:: Copyright (c) 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"
+require_relative "../knife"
class Chef
class Knife
@@ -29,6 +29,10 @@ class Chef
class ClientKeyShow < Knife
banner "knife client key show CLIENT KEYNAME (options)"
+ deps do
+ require_relative "key_show"
+ end
+
attr_reader :actor
def initialize(argv = [])
diff --git a/lib/chef/knife/client_list.rb b/lib/chef/knife/client_list.rb
index b17de0f3ad..b4fc46767b 100644
--- a/lib/chef/knife/client_list.rb
+++ b/lib/chef/knife/client_list.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,23 +16,22 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class ClientList < Knife
deps do
- require "chef/api_client_v1"
- require "chef/json_compat"
+ require_relative "../api_client_v1"
end
banner "knife client list (options)"
option :with_uri,
- :short => "-w",
- :long => "--with-uri",
- :description => "Show corresponding URIs"
+ short: "-w",
+ long: "--with-uri",
+ description: "Show corresponding URIs."
def run
output(format_list_for_display(Chef::ApiClientV1.list))
diff --git a/lib/chef/knife/client_reregister.rb b/lib/chef/knife/client_reregister.rb
index 5d9b2c0962..6741895b23 100644
--- a/lib/chef/knife/client_reregister.rb
+++ b/lib/chef/knife/client_reregister.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,23 +16,22 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class ClientReregister < Knife
deps do
- require "chef/api_client_v1"
- require "chef/json_compat"
+ require_relative "../api_client_v1"
end
banner "knife client reregister CLIENT (options)"
option :file,
- :short => "-f FILE",
- :long => "--file FILE",
- :description => "Write the key to a file"
+ short: "-f FILE",
+ long: "--file FILE",
+ description: "Write the key to a file."
def run
@client_name = @name_args[0]
@@ -44,7 +43,7 @@ class Chef
end
client = Chef::ApiClientV1.reregister(@client_name)
- Chef::Log.debug("Updated client data: #{client.inspect}")
+ Chef::Log.trace("Updated client data: #{client.inspect}")
key = client.private_key
if config[:file]
File.open(config[:file], "w") do |f|
diff --git a/lib/chef/knife/client_show.rb b/lib/chef/knife/client_show.rb
index ce3bf458b2..9170c73085 100644
--- a/lib/chef/knife/client_show.rb
+++ b/lib/chef/knife/client_show.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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"
+require_relative "../knife"
class Chef
class Knife
@@ -25,8 +25,7 @@ class Chef
include Knife::Core::MultiAttributeReturnOption
deps do
- require "chef/api_client_v1"
- require "chef/json_compat"
+ require_relative "../api_client_v1"
end
banner "knife client show CLIENT (options)"
diff --git a/lib/chef/knife/config_get.rb b/lib/chef/knife/config_get.rb
new file mode 100644
index 0000000000..91e6b7affd
--- /dev/null
+++ b/lib/chef/knife/config_get.rb
@@ -0,0 +1,39 @@
+#
+# Author:: Joshua Timberman <opensource@housepub.org>
+# Copyright:: Copyright (c) 2012, Joshua Timberman
+# Copyright:: Copyright (c) 2018, Noah Kantrowitz
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../knife"
+require_relative "./config_show"
+
+class Chef
+ class Knife
+ class ConfigGet < ConfigShow
+
+ # Handle the subclassing (knife doesn't do this :()
+ dependency_loaders.concat(superclass.dependency_loaders)
+
+ banner "knife config get [OPTION...] (options)\nDisplays the value of Chef::Config[OPTION] (or all config values)"
+ category "deprecated"
+
+ def run
+ Chef::Log.warn("knife config get has been deprecated in favor of knife config show. This will be removed in the major release version!")
+ super
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/breakpoint.rb b/lib/chef/knife/config_get_profile.rb
index a71c9e317d..a355c531fe 100644
--- a/lib/chef/provider/breakpoint.rb
+++ b/lib/chef/knife/config_get_profile.rb
@@ -1,13 +1,12 @@
#
-# Author:: Daniel DeLeo (<dan@kallistec.com>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 2018, Noah Kantrowitz
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
-# http://www.apache.org/licenses/LICENSE-2.0
+# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
@@ -16,23 +15,23 @@
# limitations under the License.
#
+require_relative "../knife"
+require_relative "./config_use"
+
class Chef
- class Provider
- class Breakpoint < Chef::Provider
+ class Knife
+ class ConfigGetProfile < ConfigUse
- provides :breakpoint
+ # Handle the subclassing (knife doesn't do this :()
+ dependency_loaders.concat(superclass.dependency_loaders)
- def load_current_resource
- end
+ banner "knife config get-profile"
+ category "deprecated"
- def action_break
- if defined?(Shell) && Shell.running?
- run_context.resource_collection.iterator.pause
- @new_resource.updated_by_last_action(true)
- run_context.resource_collection.iterator
- end
+ def run
+ Chef::Log.warn("knife config get-profiles has been deprecated in favor of knife config use. This will be removed in the major release version!")
+ super
end
-
end
end
end
diff --git a/lib/chef/knife/config_list.rb b/lib/chef/knife/config_list.rb
new file mode 100644
index 0000000000..c9f821e2a8
--- /dev/null
+++ b/lib/chef/knife/config_list.rb
@@ -0,0 +1,139 @@
+#
+# Copyright:: Copyright (c) 2018, Noah Kantrowitz
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../knife"
+
+class Chef
+ class Knife
+ class ConfigList < Knife
+ banner "knife config list (options)"
+
+ TABLE_HEADER ||= [" Profile", "Client", "Key", "Server"].freeze
+
+ deps do
+ require_relative "../workstation_config_loader"
+ require "tty-screen" unless defined?(TTY::Screen)
+ require "tty-table" unless defined?(TTY::Table)
+ end
+
+ option :ignore_knife_rb,
+ short: "-i",
+ long: "--ignore-knife-rb",
+ description: "Ignore the current config.rb/knife.rb configuration.",
+ default: false
+
+ def configure_chef
+ apply_computed_config
+ end
+
+ def run
+ credentials_data = self.class.config_loader.parse_credentials_file
+ if credentials_data.nil? || credentials_data.empty?
+ # Should this just show the ambient knife.rb config as "default" instead?
+ ui.fatal("No profiles found, #{self.class.config_loader.credentials_file_path} does not exist or is empty")
+ exit 1
+ end
+
+ current_profile = self.class.config_loader.credentials_profile(config[:profile])
+ profiles = credentials_data.keys.map do |profile|
+ if config[:ignore_knife_rb]
+ # Don't do any fancy loading nonsense, just the raw data.
+ profile_data = credentials_data[profile]
+ {
+ profile: profile,
+ active: profile == current_profile,
+ client_name: profile_data["client_name"] || profile_data["node_name"],
+ client_key: profile_data["client_key"],
+ server_url: profile_data["chef_server_url"],
+ }
+ else
+ # Fancy loading nonsense so we get what the actual config would be.
+ # Note that this modifies the global config, after this, all bets are
+ # off as to whats in the config.
+ Chef::Config.reset
+ wcl = Chef::WorkstationConfigLoader.new(nil, Chef::Log, profile: profile)
+ wcl.load
+ {
+ profile: profile,
+ active: profile == current_profile,
+ client_name: Chef::Config[:node_name],
+ client_key: Chef::Config[:client_key],
+ server_url: Chef::Config[:chef_server_url],
+ }
+ end
+ end
+
+ # Try to reset the config.
+ unless config[:ignore_knife_rb]
+ Chef::Config.reset
+ apply_computed_config
+ end
+
+ if ui.interchange?
+ # Machine-readable output.
+ ui.output(profiles)
+ else
+ # Table output.
+ ui.output(render_table(profiles))
+ end
+ end
+
+ private
+
+ def render_table(profiles, padding: 1)
+ rows = []
+ # Render the data to a 2D array that will be used for the table.
+ profiles.each do |profile|
+ # Replace the home dir in the client key path with ~.
+ profile[:client_key] = profile[:client_key].to_s.gsub(/^#{Regexp.escape(Dir.home)}/, "~") if profile[:client_key]
+ profile[:profile] = "#{profile[:active] ? "*" : " "}#{profile[:profile]}"
+ rows << profile.values_at(:profile, :client_name, :client_key, :server_url)
+ end
+
+ table = TTY::Table.new(header: TABLE_HEADER, rows: rows)
+
+ # Rotate the table to vertical if the screen width is less than table width.
+ if table.width > TTY::Screen.width
+ table.orientation = :vertical
+ table.rotate
+ # Add a new line after each profile record.
+ table.render do |renderer|
+ renderer.border do
+ separator ->(row) { (row + 1) % TABLE_HEADER.size == 0 }
+ end
+ # Remove the leading space added of the first column.
+ renderer.filter = Proc.new do |val, row_index, col_index|
+ if col_index == 1 || (row_index) % TABLE_HEADER.size == 0
+ val.strip
+ else
+ val
+ end
+ end
+ end
+ else
+ table.render do |renderer|
+ renderer.border do
+ mid "-"
+ end
+ renderer.padding = [0, padding, 0, 0] # pad right with 2 characters
+ end
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/knife/config_list_profiles.rb b/lib/chef/knife/config_list_profiles.rb
new file mode 100644
index 0000000000..c037b0de53
--- /dev/null
+++ b/lib/chef/knife/config_list_profiles.rb
@@ -0,0 +1,37 @@
+#
+# Copyright:: Copyright (c) 2018, Noah Kantrowitz
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../knife"
+require_relative "./config_list"
+
+class Chef
+ class Knife
+ class ConfigListProfiles < ConfigList
+
+ # Handle the subclassing (knife doesn't do this :()
+ dependency_loaders.concat(superclass.dependency_loaders)
+
+ banner "knife config list-profiles (options)"
+ category "deprecated"
+
+ def run
+ Chef::Log.warn("knife config list-profiles has been deprecated in favor of knife config list. This will be removed in the major release version!")
+ super
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/config_show.rb b/lib/chef/knife/config_show.rb
new file mode 100644
index 0000000000..7f28891885
--- /dev/null
+++ b/lib/chef/knife/config_show.rb
@@ -0,0 +1,127 @@
+#
+# Author:: Vivek Singh (<vsingh@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../knife"
+
+class Chef
+ class Knife
+ class ConfigShow < Knife
+ banner "knife config show [OPTION...] (options)\nDisplays the value of Chef::Config[OPTION] (or all config values)"
+
+ option :all,
+ short: "-a",
+ long: "--all",
+ description: "Include options that are not set in the configuration.",
+ default: false
+
+ option :raw,
+ short: "-r",
+ long: "--raw",
+ description: "Display a each value with no formatting.",
+ default: false
+
+ def run
+ if config[:format] == "summary" && !config[:raw]
+ # If using the default, human-readable output, also show which config files are being loaded.
+ # Some of this is a bit hacky since it duplicates
+ wcl = self.class.config_loader
+ if wcl.credentials_found
+ loading_from("credentials", ChefConfig::PathHelper.home(".chef", "credentials"))
+ end
+ if wcl.config_location
+ loading_from("configuration", wcl.config_location)
+ end
+
+ if Chef::Config[:config_d_dir]
+ wcl.find_dot_d(Chef::Config[:config_d_dir]).each do |path|
+ loading_from(".d/ configuration", path)
+ end
+ end
+ end
+
+ # Dump the whole config, including defaults is --all was given.
+ config_data = Chef::Config.save(config[:all])
+ # Two special cases, these are set during knife startup but we don't usually care about them.
+ unless config[:all]
+ config_data.delete(:color)
+ # Only keep these if true, false is much less important because it's the default.
+ config_data.delete(:local_mode) unless config_data[:local_mode]
+ config_data.delete(:enforce_default_paths) unless config_data[:enforce_default_paths]
+ config_data.delete(:enforce_path_sanity) unless config_data[:enforce_path_sanity]
+ end
+
+ # Extract the data to show.
+ output_data = {}
+ if @name_args.empty?
+ output_data = config_data
+ else
+ @name_args.each do |filter|
+ if filter =~ %r{^/(.*)/(i?)$}
+ # It's a regex.
+ filter_re = Regexp.new($1, $2 ? Regexp::IGNORECASE : 0)
+ config_data.each do |key, value|
+ output_data[key] = value if key.to_s&.match?(filter_re)
+ end
+ else
+ # It's a dotted path string.
+ filter_parts = filter.split(".")
+ extract = lambda do |memo, filter_part|
+ memo.is_a?(Hash) ? memo[filter_part.to_sym] : nil
+ end
+ # Check against both config_data and all of the data, so that even
+ # in non-all mode, if you ask for a key that isn't in the non-all
+ # data, it will check against the broader set.
+ output_data[filter] = filter_parts.inject(config_data, &extract) || filter_parts.inject(Chef::Config.save(true), &extract)
+ end
+ end
+ end
+
+ # Fix up some values.
+ output_data.each do |key, value|
+ if value == STDOUT
+ output_data[key] = "STDOUT"
+ elsif value == STDERR
+ output_data[key] = "STDERR"
+ end
+ end
+
+ # Show the data.
+ if config[:raw]
+ output_data.each_value do |value|
+ ui.msg(value)
+ end
+ else
+ ui.output(output_data)
+ end
+ end
+
+ private
+
+ # Display a banner about loading from a config file.
+ #
+ # @api private
+ # @param type_of_file [String] Description of the file for the banner.
+ # @param path [String] Path of the file.
+ # @return [nil]
+ def loading_from(type_of_file, path)
+ path = Pathname.new(path).realpath
+ ui.msg(ui.color("Loading from #{type_of_file} file #{path}", :yellow))
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/config_use.rb b/lib/chef/knife/config_use.rb
new file mode 100644
index 0000000000..e944dc210b
--- /dev/null
+++ b/lib/chef/knife/config_use.rb
@@ -0,0 +1,61 @@
+#
+# Author:: Vivek Singh (<vsingh@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../knife"
+
+class Chef
+ class Knife
+ class ConfigUse < Knife
+ banner "knife config use [PROFILE]"
+
+ deps do
+ require "fileutils" unless defined?(FileUtils)
+ end
+
+ # Disable normal config loading since this shouldn't fail if the profile
+ # doesn't exist of the config is otherwise corrupted.
+ def configure_chef
+ apply_computed_config
+ end
+
+ def run
+ profile = @name_args[0]&.strip
+ if profile.nil? || profile.empty?
+ ui.msg(self.class.config_loader.credentials_profile(config[:profile]))
+ else
+ credentials_data = self.class.config_loader.parse_credentials_file
+ context_file = ChefConfig::PathHelper.home(".chef", "context").freeze
+
+ if credentials_data.nil? || credentials_data.empty?
+ ui.fatal("No profiles found, #{self.class.config_loader.credentials_file_path} does not exist or is empty")
+ exit 1
+ end
+
+ if credentials_data[profile].nil?
+ raise ChefConfig::ConfigurationError, "Profile #{profile} doesn't exist. Please add it to #{self.class.config_loader.credentials_file_path} and if it is profile with DNS name check that you are not missing single quotes around it as per docs https://docs.chef.io/workstation/knife_setup/#knife-profiles."
+ else
+ # Ensure the .chef/ folder exists.
+ FileUtils.mkdir_p(File.dirname(context_file))
+ IO.write(context_file, "#{profile}\n")
+ ui.msg("Set default profile to #{profile}")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/config_use_profile.rb b/lib/chef/knife/config_use_profile.rb
new file mode 100644
index 0000000000..169bdbef30
--- /dev/null
+++ b/lib/chef/knife/config_use_profile.rb
@@ -0,0 +1,47 @@
+#
+# Copyright:: Copyright (c) 2018, Noah Kantrowitz
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../knife"
+require_relative "./config_use"
+
+class Chef
+ class Knife
+ class ConfigUseProfile < ConfigUse
+
+ # Handle the subclassing (knife doesn't do this :()
+ dependency_loaders.concat(superclass.dependency_loaders)
+
+ banner "knife config use-profile PROFILE"
+ category "deprecated"
+
+ def run
+ Chef::Log.warn("knife config use-profile has been deprecated in favor of knife config use. This will be removed in the major release version!")
+
+ credentials_data = self.class.config_loader.parse_credentials_file
+ context_file = ChefConfig::PathHelper.home(".chef", "context").freeze
+ profile = @name_args[0]&.strip
+ if profile.nil? || profile.empty?
+ show_usage
+ ui.fatal("You must specify a profile")
+ exit 1
+ end
+
+ super
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/configure.rb b/lib/chef/knife/configure.rb
index e726e32684..2a27fd5d88 100644
--- a/lib/chef/knife/configure.rb
+++ b/lib/chef/knife/configure.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,7 +16,8 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Knife
@@ -25,7 +26,10 @@ class Chef
attr_reader :chef_repo, :new_client_key, :validation_client_name, :validation_key
deps do
- require "ohai"
+ require_relative "../util/path_helper"
+ require_relative "client_create"
+ require_relative "user_create"
+ require "ohai" unless defined?(Ohai::System)
Chef::Knife::ClientCreate.load_deps
Chef::Knife::UserCreate.load_deps
end
@@ -33,31 +37,31 @@ class Chef
banner "knife configure (options)"
option :repository,
- :short => "-r REPO",
- :long => "--repository REPO",
- :description => "The path to the chef-repo"
+ short: "-r REPO",
+ long: "--repository REPO",
+ description: "The path to the chef-repo."
option :initial,
- :short => "-i",
- :long => "--initial",
- :boolean => true,
- :description => "Use to create a API client, typically an administrator client on a freshly-installed server"
+ short: "-i",
+ long: "--initial",
+ boolean: true,
+ description: "Use to create a API client, typically an administrator client on a freshly-installed server."
option :admin_client_name,
- :long => "--admin-client-name NAME",
- :description => "The name of the client, typically the name of the admin client"
+ long: "--admin-client-name NAME",
+ description: "The name of the client, typically the name of the admin client."
option :admin_client_key,
- :long => "--admin-client-key PATH",
- :description => "The path to the private key used by the client, typically a file named admin.pem"
+ long: "--admin-client-key PATH",
+ description: "The path to the private key used by the client, typically a file named admin.pem."
option :validation_client_name,
- :long => "--validation-client-name NAME",
- :description => "The name of the validation client, typically a client named chef-validator"
+ long: "--validation-client-name NAME",
+ description: "The name of the validation client, typically a client named chef-validator."
option :validation_key,
- :long => "--validation-key PATH",
- :description => "The path to the validation key used by the client, typically a file named validation.pem"
+ long: "--validation-key PATH",
+ description: "The path to the validation key used by the client, typically a file named validation.pem."
def configure_chef
# We are just faking out the system so that you can do this without a key specified
@@ -67,26 +71,19 @@ class Chef
end
def run
- ask_user_for_config_path
-
FileUtils.mkdir_p(chef_config_path)
ask_user_for_config
- ::File.open(config[:config_file], "w") do |f|
- f.puts <<-EOH
-log_level :info
-log_location STDOUT
-node_name '#{new_client_name}'
-client_key '#{new_client_key}'
-validation_client_name '#{validation_client_name}'
-validation_key '#{validation_key}'
-chef_server_url '#{chef_server}'
-syntax_check_cache_path '#{File.join(chef_config_path, "syntax_check_cache")}'
-EOH
- unless chef_repo.empty?
- f.puts "cookbook_path [ '#{chef_repo}/cookbooks' ]"
- end
+ confirm("Overwrite #{config_file_path}") if ::File.exist?(config_file_path)
+
+ ::File.open(config_file_path, "w") do |f|
+ f.puts <<~EOH
+ [default]
+ client_name = '#{new_client_name}'
+ client_key = '#{new_client_key}'
+ chef_server_url = '#{chef_server}'
+ EOH
end
if config[:initial]
@@ -97,7 +94,7 @@ EOH
user_create = Chef::Knife::UserCreate.new
user_create.name_args = [ new_client_name ]
user_create.config[:user_password] = config[:user_password] ||
- ui.ask("Please enter a password for the new user: ") { |q| q.echo = false }
+ ui.ask("Please enter a password for the new user: ", echo: false)
user_create.config[:admin] = true
user_create.config[:file] = new_client_key
user_create.config[:yes] = true
@@ -111,60 +108,42 @@ EOH
ui.msg("Before running commands with Knife")
ui.msg("")
ui.msg("*****")
- ui.msg("")
- ui.msg("You must place your validation key in:")
- ui.msg(" #{validation_key}")
- ui.msg("Before generating instance data with Knife")
- ui.msg("")
- ui.msg("*****")
end
- ui.msg("Configuration file written to #{config[:config_file]}")
- end
-
- def ask_user_for_config_path
- config[:config_file] ||= ask_question("Where should I put the config file? ", :default => "#{Chef::Config[:user_home]}/.chef/knife.rb")
- # have to use expand path to expand the tilde character to the user's home
- config[:config_file] = File.expand_path(config[:config_file])
- if File.exists?(config[:config_file])
- confirm("Overwrite #{config[:config_file]}")
- end
+ ui.msg("Knife configuration file written to #{config_file_path}")
end
def ask_user_for_config
server_name = guess_servername
- @chef_server = config[:chef_server_url] || ask_question("Please enter the chef server URL: ", :default => "https://#{server_name}:443")
+ @chef_server = config[:chef_server_url] || ask_question("Please enter the chef server URL: ", default: "https://#{server_name}/organizations/myorg")
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")
+ @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: "#{ChefUtils::Dist::Server::CONF_DIR}/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)
+ @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_key = File.expand_path(@validation_key)
- @chef_repo = config[:repository] || ask_question("Please enter the path to a chef repository (or leave blank): ")
@new_client_key = config[:client_key] || File.join(chef_config_path, "#{@new_client_name}.pem")
@new_client_key = File.expand_path(@new_client_key)
end
+ # @return [String] our best guess at what the servername should be using Ohai data and falling back to localhost
def guess_servername
o = Ohai::System.new
- o.load_plugins
- o.require_plugin "os"
- o.require_plugin "hostname"
+ o.all_plugins(%w{ os hostname fqdn })
o[:fqdn] || o[:machinename] || o[:hostname] || "localhost"
end
- def config_file
- config[:config_file]
+ # @return [String] the path to the user's .chef directory
+ def chef_config_path
+ @chef_config_path ||= Chef::Util::PathHelper.home(".chef")
end
- def chef_config_path
- File.dirname(config_file)
+ # @return [String] the full path to the config file (credential file)
+ def config_file_path
+ @config_file_path ||= ::File.expand_path(::File.join(chef_config_path, "credentials"))
end
end
end
diff --git a/lib/chef/knife/configure_client.rb b/lib/chef/knife/configure_client.rb
index 7d0b3d260d..c6f159ec8f 100644
--- a/lib/chef/knife/configure_client.rb
+++ b/lib/chef/knife/configure_client.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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"
+require_relative "../knife"
class Chef
class Knife
@@ -34,8 +34,6 @@ 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("chef_server_url '#{Chef::Config[:chef_server_url]}'")
file.puts("validation_client_name '#{Chef::Config[:validation_client_name]}'")
end
diff --git a/lib/chef/knife/cookbook_bulk_delete.rb b/lib/chef/knife/cookbook_bulk_delete.rb
index cdd1584e36..d6657ccb4f 100644
--- a/lib/chef/knife/cookbook_bulk_delete.rb
+++ b/lib/chef/knife/cookbook_bulk_delete.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/knife"
+require_relative "../knife"
class Chef
class Knife
class CookbookBulkDelete < Knife
deps do
- require "chef/knife/cookbook_delete"
- require "chef/cookbook_version"
+ require_relative "cookbook_delete"
+ require_relative "../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)"
diff --git a/lib/chef/knife/cookbook_create.rb b/lib/chef/knife/cookbook_create.rb
deleted file mode 100644
index ccb78bb7a6..0000000000
--- a/lib/chef/knife/cookbook_create.rb
+++ /dev/null
@@ -1,462 +0,0 @@
-#
-# Author:: Nuo Yan (<nuo@chef.io>)
-# Copyright:: Copyright 2009-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/knife"
-
-class Chef
- class Knife
- class CookbookCreate < Knife
-
- deps do
- require "chef/json_compat"
- require "uri"
- require "fileutils"
- end
-
- banner "knife cookbook create COOKBOOK (options)"
-
- option :cookbook_path,
- :short => "-o PATH",
- :long => "--cookbook-path PATH",
- :description => "The directory where the cookbook will be created"
-
- option :readme_format,
- :short => "-r FORMAT",
- :long => "--readme-format FORMAT",
- :description => "Format of the README file, supported formats are 'md' (markdown) and 'rdoc' (rdoc)"
-
- option :cookbook_license,
- :short => "-I LICENSE",
- :long => "--license LICENSE",
- :description => "License for cookbook, apachev2, gplv2, gplv3, mit or none"
-
- option :cookbook_copyright,
- :short => "-C COPYRIGHT",
- :long => "--copyright COPYRIGHT",
- :description => "Name of copyright holder"
-
- option :cookbook_email,
- :short => "-m EMAIL",
- :long => "--email EMAIL",
- :description => "Email address of cookbook maintainer"
-
- def run
- Chef::Log.deprecation <<EOF
-This command is being deprecated in favor of `chef generate cookbook` and will soon return an error.
-Please use `chef generate cookbook` instead of this command.
-EOF
- self.config = Chef::Config.merge!(config)
- if @name_args.length < 1
- show_usage
- ui.fatal("You must specify a cookbook name")
- exit 1
- end
-
- if default_cookbook_path_empty? && parameter_empty?(config[:cookbook_path])
- raise ArgumentError, "Default cookbook_path is not specified in the knife.rb config file, and a value to -o is not provided. Nowhere to write the new cookbook to."
- end
-
- cookbook_path = File.expand_path(Array(config[:cookbook_path]).first)
- cookbook_name = @name_args.first
- copyright = config[:cookbook_copyright] || "YOUR_COMPANY_NAME"
- email = config[:cookbook_email] || "YOUR_EMAIL"
- license = ((config[:cookbook_license] != "false") && config[:cookbook_license]) || "none"
- readme_format = ((config[:readme_format] != "false") && config[:readme_format]) || "md"
- create_cookbook(cookbook_path, cookbook_name, copyright, license)
- create_readme(cookbook_path, cookbook_name, readme_format)
- create_changelog(cookbook_path, cookbook_name)
- create_metadata(cookbook_path, cookbook_name, copyright, email, license, readme_format)
- end
-
- def create_cookbook(dir, cookbook_name, copyright, license)
- msg("** Creating cookbook #{cookbook_name} in #{dir}")
- FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "attributes")}"
- FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "recipes")}"
- FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "definitions")}"
- FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "libraries")}"
- FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "resources")}"
- FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "providers")}"
- FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "files", "default")}"
- FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "templates", "default")}"
- unless File.exists?(File.join(dir, cookbook_name, "recipes", "default.rb"))
- open(File.join(dir, cookbook_name, "recipes", "default.rb"), "w") do |file|
- file.puts <<-EOH
-#
-# Cookbook Name:: #{cookbook_name}
-# Recipe:: default
-#
-# Copyright #{Time.now.year}, #{copyright}
-#
-EOH
- case license
- when "apachev2"
- file.puts <<-EOH
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-EOH
- when "gplv2"
- file.puts <<-EOH
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-EOH
- when "gplv3"
- file.puts <<-EOH
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-EOH
- when "mit"
- file.puts <<-EOH
-# 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.
-#
-EOH
- when "none"
- file.puts <<-EOH
-# All rights reserved - Do Not Redistribute
-#
-EOH
- end
- end
- end
- end
-
- def create_changelog(dir, cookbook_name)
- msg("** Creating CHANGELOG for cookbook: #{cookbook_name}")
- unless File.exists?(File.join(dir, cookbook_name, "CHANGELOG.md"))
- open(File.join(dir, cookbook_name, "CHANGELOG.md"), "w") do |file|
- file.puts <<-EOH
-# #{cookbook_name} CHANGELOG
-
-This file is used to list changes made in each version of the #{cookbook_name} cookbook.
-
-## 0.1.0
-- [your_name] - Initial release of #{cookbook_name}
-
-- - -
-Check the [Markdown Syntax Guide](http://daringfireball.net/projects/markdown/syntax) for help with Markdown.
-
-The [Github Flavored Markdown page](http://github.github.com/github-flavored-markdown/) describes the differences between markdown on github and standard markdown.
-EOH
- end
- end
- end
-
- def create_readme(dir, cookbook_name, readme_format)
- msg("** Creating README for cookbook: #{cookbook_name}")
- unless File.exist?(File.join(dir, cookbook_name, "README.#{readme_format}"))
- open(File.join(dir, cookbook_name, "README.#{readme_format}"), "w") do |file|
- case readme_format
- when "rdoc"
- file.puts <<-EOH
-= #{cookbook_name} Cookbook
-TODO: Enter the cookbook description here.
-
-e.g.
-This cookbook makes your favorite breakfast sandwich.
-
-== Requirements
-TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc.
-
-e.g.
-==== packages
-- +toaster+ - #{cookbook_name} needs toaster to brown your bagel.
-
-== Attributes
-TODO: List your cookbook attributes here.
-
-e.g.
-==== #{cookbook_name}::default
-<table>
- <tr>
- <th>Key</th>
- <th>Type</th>
- <th>Description</th>
- <th>Default</th>
- </tr>
- <tr>
- <td><tt>['#{cookbook_name}']['bacon']</tt></td>
- <td>Boolean</td>
- <td>whether to include bacon</td>
- <td><tt>true</tt></td>
- </tr>
-</table>
-
-== Usage
-==== #{cookbook_name}::default
-TODO: Write usage instructions for each cookbook.
-
-e.g.
-Just include +#{cookbook_name}+ in your node's +run_list+:
-
- {
- "name":"my_node",
- "run_list": [
- "recipe[#{cookbook_name}]"
- ]
- }
-
-== Contributing
-TODO: (optional) If this is a public cookbook, detail the process for contributing. If this is a private cookbook, remove this section.
-
-e.g.
-1. Fork the repository on Github
-2. Create a named feature branch (like `add_component_x`)
-3. Write your change
-4. Write tests for your change (if applicable)
-5. Run the tests, ensuring they all pass
-6. Submit a Pull Request using Github
-
-== License and Authors
-Authors: TODO: List authors
-EOH
- when "md", "mkd", "txt"
- file.puts <<-EOH
-# #{cookbook_name} Cookbook
-
-TODO: Enter the cookbook description here.
-
-e.g.
-This cookbook makes your favorite breakfast sandwich.
-
-## Requirements
-
-TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc.
-
-e.g.
-### Platforms
-
-- SandwichOS
-
-### Chef
-
-- Chef 12.0 or later
-
-### Cookbooks
-
-- `toaster` - #{cookbook_name} needs toaster to brown your bagel.
-
-## Attributes
-
-TODO: List your cookbook attributes here.
-
-e.g.
-### #{cookbook_name}::default
-
-<table>
- <tr>
- <th>Key</th>
- <th>Type</th>
- <th>Description</th>
- <th>Default</th>
- </tr>
- <tr>
- <td><tt>['#{cookbook_name}']['bacon']</tt></td>
- <td>Boolean</td>
- <td>whether to include bacon</td>
- <td><tt>true</tt></td>
- </tr>
-</table>
-
-## Usage
-
-### #{cookbook_name}::default
-
-TODO: Write usage instructions for each cookbook.
-
-e.g.
-Just include `#{cookbook_name}` in your node's `run_list`:
-
-```json
-{
- "name":"my_node",
- "run_list": [
- "recipe[#{cookbook_name}]"
- ]
-}
-```
-
-## Contributing
-
-TODO: (optional) If this is a public cookbook, detail the process for contributing. If this is a private cookbook, remove this section.
-
-e.g.
-1. Fork the repository on Github
-2. Create a named feature branch (like `add_component_x`)
-3. Write your change
-4. Write tests for your change (if applicable)
-5. Run the tests, ensuring they all pass
-6. Submit a Pull Request using Github
-
-## License and Authors
-
-Authors: TODO: List authors
-
-EOH
- else
- file.puts <<-EOH
-#{cookbook_name} Cookbook
-#{'=' * "#{cookbook_name} Cookbook".length}
- TODO: Enter the cookbook description here.
-
- e.g.
- This cookbook makes your favorite breakfast sandwich.
-
-Requirements
- TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc.
-
- e.g.
- toaster #{cookbook_name} needs toaster to brown your bagel.
-
-Attributes
- TODO: List your cookbook attributes here.
-
- #{cookbook_name}
- Key Type Description Default
- ['#{cookbook_name}']['bacon'] Boolean whether to include bacon true
-
-Usage
- #{cookbook_name}
- TODO: Write usage instructions for each cookbook.
-
- e.g.
- Just include `#{cookbook_name}` in your node's `run_list`:
-
- [code]
- {
- "name":"my_node",
- "run_list": [
- "recipe[#{cookbook_name}]"
- ]
- }
- [/code]
-
-Contributing
- TODO: (optional) If this is a public cookbook, detail the process for contributing. If this is a private cookbook, remove this section.
-
- e.g.
- 1. Fork the repository on Github
- 2. Create a named feature branch (like `add_component_x`)
- 3. Write your change
- 4. Write tests for your change (if applicable)
- 5. Run the tests, ensuring they all pass
- 6. Submit a Pull Request using Github
-
-License and Authors
- Authors: TODO: List authors
-EOH
- end
- end
- end
- end
-
- def create_metadata(dir, cookbook_name, copyright, email, license, readme_format)
- msg("** Creating metadata for cookbook: #{cookbook_name}")
-
- license_name = case license
- when "apachev2"
- "Apache 2.0"
- when "gplv2"
- "GNU Public License 2.0"
- when "gplv3"
- "GNU Public License 3.0"
- when "mit"
- "MIT"
- when "none"
- "All rights reserved"
- end
-
- unless File.exist?(File.join(dir, cookbook_name, "metadata.rb"))
- open(File.join(dir, cookbook_name, "metadata.rb"), "w") do |file|
- if File.exist?(File.join(dir, cookbook_name, "README.#{readme_format}"))
- long_description = "long_description IO.read(File.join(File.dirname(__FILE__), 'README.#{readme_format}'))"
- end
- file.puts <<-EOH
-name '#{cookbook_name}'
-maintainer '#{copyright}'
-maintainer_email '#{email}'
-license '#{license_name}'
-description 'Installs/Configures #{cookbook_name}'
-#{long_description}
-version '0.1.0'
-EOH
- end
- end
- end
-
- private
-
- def default_cookbook_path_empty?
- Chef::Config[:cookbook_path].nil? || Chef::Config[:cookbook_path].empty?
- end
-
- def parameter_empty?(parameter)
- parameter.nil? || parameter.empty?
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/cookbook_delete.rb b/lib/chef/knife/cookbook_delete.rb
index b1bb88b388..04ecb95cf4 100644
--- a/lib/chef/knife/cookbook_delete.rb
+++ b/lib/chef/knife/cookbook_delete.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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"
+require_relative "../knife"
class Chef
class Knife
@@ -25,12 +25,12 @@ class Chef
attr_accessor :cookbook_name, :version
deps do
- require "chef/cookbook_version"
+ require_relative "../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 of the cookbook."
- 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)"
@@ -45,7 +45,7 @@ class Chef
delete_without_explicit_version
elsif @cookbook_name.nil?
show_usage
- ui.fatal("You must provide the name of the cookbook to delete")
+ ui.fatal("You must provide the name of the cookbook to delete.")
exit(1)
end
end
@@ -88,9 +88,9 @@ class Chef
@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
- if e.to_s =~ /^404/
- ui.error("Cannot find a cookbook named #{@cookbook_name} to delete")
+ rescue Net::HTTPClientException => e
+ if /^404/.match?(e.to_s)
+ ui.error("Cannot find a cookbook named #{@cookbook_name} to delete.")
nil
else
raise
@@ -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(&:strip)
if responses.empty?
ui.error("No versions specified, exiting")
diff --git a/lib/chef/knife/cookbook_download.rb b/lib/chef/knife/cookbook_download.rb
index 741f444093..a07b519511 100644
--- a/lib/chef/knife/cookbook_download.rb
+++ b/lib/chef/knife/cookbook_download.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/knife"
+require_relative "../knife"
class Chef
class Knife
@@ -27,27 +27,27 @@ class Chef
attr_accessor :cookbook_name
deps do
- require "chef/cookbook_version"
+ require_relative "../cookbook_version"
end
banner "knife cookbook download COOKBOOK [VERSION] (options)"
option :latest,
- :short => "-N",
- :long => "--latest",
- :description => "The version of the cookbook to download",
- :boolean => true
+ short: "-N",
+ long: "--latest",
+ description: "The version of the cookbook to download.",
+ boolean: true
option :download_directory,
- :short => "-d DOWNLOAD_DIRECTORY",
- :long => "--dir DOWNLOAD_DIRECTORY",
- :description => "The directory to download the cookbook into",
- :default => Dir.pwd
+ short: "-d DOWNLOAD_DIRECTORY",
+ long: "--dir DOWNLOAD_DIRECTORY",
+ description: "The directory to download the cookbook into.",
+ default: Dir.pwd
option :force,
- :short => "-f",
- :long => "--force",
- :description => "Force download over the download directory if it exists"
+ short: "-f",
+ long: "--force",
+ description: "Force download over the download directory if it exists."
# TODO: tim/cw: 5-23-2010: need to implement knife-side
# specificity for downloads - need to implement --platform and
@@ -70,12 +70,12 @@ class Chef
ui.info("Downloading #{@cookbook_name} cookbook version #{@version}")
cookbook = Chef::CookbookVersion.load(@cookbook_name, @version)
- manifest = cookbook.manifest
+ manifest = cookbook.cookbook_manifest
basedir = File.join(config[:download_directory], "#{@cookbook_name}-#{cookbook.version}")
- if File.exists?(basedir)
+ if File.exist?(basedir)
if config[:force]
- Chef::Log.debug("Deleting #{basedir}")
+ Chef::Log.trace("Deleting #{basedir}")
FileUtils.rm_rf(basedir)
else
ui.fatal("Directory #{basedir} exists, use --force to overwrite")
@@ -83,12 +83,11 @@ class Chef
end
end
- Chef::CookbookVersion::COOKBOOK_SEGMENTS.each do |segment|
- next unless manifest.has_key?(segment)
+ manifest.by_parent_directory.each do |segment, files|
ui.info("Downloading #{segment}")
- manifest[segment].each do |segment_file|
+ files.each do |segment_file|
dest = File.join(basedir, segment_file["path"].gsub("/", File::SEPARATOR))
- Chef::Log.debug("Downloading #{segment_file['path']} to #{dest}")
+ Chef::Log.trace("Downloading #{segment_file["path"]} to #{dest}")
FileUtils.mkdir_p(File.dirname(dest))
tempfile = rest.streaming_request(segment_file["url"])
FileUtils.mv(tempfile.path, dest)
diff --git a/lib/chef/knife/cookbook_list.rb b/lib/chef/knife/cookbook_list.rb
index ea81f5d286..719c10f893 100644
--- a/lib/chef/knife/cookbook_list.rb
+++ b/lib/chef/knife/cookbook_list.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Nuo Yan (<nuo@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/knife"
+require_relative "../knife"
class Chef
class Knife
@@ -26,14 +26,14 @@ class Chef
banner "knife cookbook list (options)"
option :with_uri,
- :short => "-w",
- :long => "--with-uri",
- :description => "Show corresponding URIs"
+ short: "-w",
+ long: "--with-uri",
+ description: "Show corresponding URIs."
option :all_versions,
- :short => "-a",
- :long => "--all",
- :description => "Show all available versions."
+ short: "-a",
+ long: "--all",
+ description: "Show all available versions."
def run
env = config[:environment]
diff --git a/lib/chef/knife/cookbook_metadata.rb b/lib/chef/knife/cookbook_metadata.rb
index 29eba6a36a..8d8970b1c1 100644
--- a/lib/chef/knife/cookbook_metadata.rb
+++ b/lib/chef/knife/cookbook_metadata.rb
@@ -1,7 +1,6 @@
#
-#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,29 +16,29 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class CookbookMetadata < Knife
deps do
- require "chef/cookbook_loader"
- require "chef/cookbook/metadata"
+ require_relative "../cookbook_loader"
+ require_relative "../cookbook/metadata"
end
banner "knife cookbook metadata COOKBOOK (options)"
option :cookbook_path,
- :short => "-o PATH:PATH",
- :long => "--cookbook-path PATH:PATH",
- :description => "A colon-separated path to look for cookbooks in",
- :proc => lambda { |o| o.split(":") }
+ short: "-o PATH:PATH",
+ long: "--cookbook-path PATH:PATH",
+ description: "A colon-separated path to look for cookbooks in.",
+ proc: lambda { |o| o.split(":") }
option :all,
- :short => "-a",
- :long => "--all",
- :description => "Generate metadata for all cookbooks, rather than just a single cookbook"
+ short: "-a",
+ long: "--all",
+ description: "Generate metadata for all cookbooks, rather than just a single cookbook."
def run
config[:cookbook_path] ||= Chef::Config[:cookbook_path]
@@ -47,7 +46,7 @@ class Chef
if config[:all]
cl = Chef::CookbookLoader.new(config[:cookbook_path])
cl.load_cookbooks
- cl.each do |cname, cookbook|
+ cl.each_key do |cname|
generate_metadata(cname.to_s)
end
else
@@ -63,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"))
- if File.exists?(file)
+ if File.exist?(file)
generate_metadata_from_file(cookbook, file)
else
validate_metadata_json(path, cookbook)
@@ -80,7 +79,7 @@ class Chef
File.open(json_file, "w") do |f|
f.write(Chef::JSONCompat.to_json_pretty(md))
end
- Chef::Log.debug("Generated #{json_file}")
+ Chef::Log.trace("Generated #{json_file}")
rescue Exceptions::ObsoleteDependencySyntax, Exceptions::InvalidVersionConstraint => e
ui.stderr.puts "ERROR: The cookbook '#{cookbook}' contains invalid or obsolete metadata syntax."
ui.stderr.puts "in #{file}:"
diff --git a/lib/chef/knife/cookbook_metadata_from_file.rb b/lib/chef/knife/cookbook_metadata_from_file.rb
index ec46379da7..d768213384 100644
--- a/lib/chef/knife/cookbook_metadata_from_file.rb
+++ b/lib/chef/knife/cookbook_metadata_from_file.rb
@@ -1,8 +1,7 @@
#
-#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Matthew Kent (<mkent@magoazul.com>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# Copyright:: Copyright 2010-2016, Matthew Kent
# License:: Apache License, Version 2.0
#
@@ -19,19 +18,25 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class CookbookMetadataFromFile < Knife
deps do
- require "chef/cookbook/metadata"
+ require_relative "../cookbook/metadata"
end
- banner "knife cookbook metadata from FILE (options)"
+ banner "knife cookbook metadata from file FILE (options)"
def run
+ if @name_args.length < 1
+ show_usage
+ ui.fatal("You must specify the FILE.")
+ exit(1)
+ end
+
file = @name_args[0]
cookbook = File.basename(File.dirname(file))
diff --git a/lib/chef/knife/cookbook_show.rb b/lib/chef/knife/cookbook_show.rb
index d0c930de0a..0b97fba139 100644
--- a/lib/chef/knife/cookbook_show.rb
+++ b/lib/chef/knife/cookbook_show.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,39 +16,39 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class CookbookShow < Knife
deps do
- require "chef/json_compat"
- require "uri"
- require "chef/cookbook_version"
+ require_relative "../json_compat"
+ require "uri" unless defined?(URI)
+ require_relative "../cookbook_version"
end
banner "knife cookbook show COOKBOOK [VERSION] [PART] [FILENAME] (options)"
option :fqdn,
- :short => "-f FQDN",
- :long => "--fqdn FQDN",
- :description => "The FQDN of the host to see the file for"
+ short: "-f FQDN",
+ long: "--fqdn FQDN",
+ description: "The FQDN of the host to see the file for."
option :platform,
- :short => "-p PLATFORM",
- :long => "--platform PLATFORM",
- :description => "The platform to see the file for"
+ short: "-p PLATFORM",
+ long: "--platform PLATFORM",
+ description: "The platform to see the file for."
option :platform_version,
- :short => "-V VERSION",
- :long => "--platform-version VERSION",
- :description => "The platform version to see the file for"
+ short: "-V VERSION",
+ long: "--platform-version VERSION",
+ description: "The platform version to see the file for."
option :with_uri,
- :short => "-w",
- :long => "--with-uri",
- :description => "Show corresponding URIs"
+ short: "-w",
+ long: "--with-uri",
+ description: "Show corresponding URIs."
def run
cookbook_name, cookbook_version, segment, filename = @name_args
@@ -57,14 +57,14 @@ class Chef
case @name_args.length
when 4 # We are showing a specific file
- node = Hash.new
- node[:fqdn] = config[:fqdn] if config.has_key?(:fqdn)
- node[:platform] = config[:platform] if config.has_key?(:platform)
- node[:platform_version] = config[:platform_version] if config.has_key?(:platform_version)
+ node = {}
+ node[:fqdn] = config[:fqdn] if config.key?(:fqdn)
+ node[:platform] = config[:platform] if config.key?(:platform)
+ node[:platform_version] = config[:platform_version] if config.key?(:platform_version)
class << node
def attribute?(name) # rubocop:disable Lint/NestedMethodDefinition
- has_key?(name)
+ key?(name)
end
end
@@ -76,9 +76,13 @@ class Chef
pretty_print(temp_file.read)
when 3 # We are showing a specific part of the cookbook
- output(cookbook.manifest[segment])
- when 2 # We are showing the whole cookbook data
- output(cookbook)
+ if segment == "metadata"
+ output(cookbook.metadata)
+ else
+ output(cookbook.files_for(segment))
+ end
+ when 2 # We are showing the whole cookbook
+ output(cookbook.display)
when 1 # We are showing the cookbook versions (all of them)
env = config[:environment]
api_endpoint = env ? "environments/#{env}/cookbooks/#{cookbook_name}" : "cookbooks/#{cookbook_name}"
diff --git a/lib/chef/knife/cookbook_site_download.rb b/lib/chef/knife/cookbook_site_download.rb
deleted file mode 100644
index 43677cfa78..0000000000
--- a/lib/chef/knife/cookbook_site_download.rb
+++ /dev/null
@@ -1,116 +0,0 @@
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-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/knife"
-
-class Chef
- class Knife
- class CookbookSiteDownload < Knife
-
- deps do
- require "fileutils"
- end
-
- banner "knife cookbook site download COOKBOOK [VERSION] (options)"
- category "cookbook site"
-
- option :file,
- :short => "-f FILE",
- :long => "--file FILE",
- :description => "The filename to write to"
-
- option :force,
- :long => "--force",
- :description => "Force download deprecated version"
-
- option :supermarket_site,
- :short => "-m SUPERMARKET_SITE",
- :long => "--supermarket-site SUPERMARKET_SITE",
- :description => "Supermarket Site",
- :default => "https://supermarket.chef.io",
- :proc => Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket }
-
- def run
- if current_cookbook_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."
- return
- end
- end
-
- download_cookbook
- end
-
- def version
- @version = desired_cookbook_data["version"]
- end
-
- private
-
- def cookbooks_api_url
- "#{config[:supermarket_site]}/api/v1/cookbooks"
- end
-
- def current_cookbook_data
- @current_cookbook_data ||= begin
- noauth_rest.get "#{cookbooks_api_url}/#{@name_args[0]}"
- end
- end
-
- def current_cookbook_deprecated?
- 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 uri
- end
- end
-
- def download_cookbook
- ui.info "Downloading #{@name_args[0]} from Supermarket at version #{version} to #{download_location}"
- tf = noauth_rest.streaming_request(desired_cookbook_data["file"])
-
- ::FileUtils.cp tf.path, download_location
- ui.info "Cookbook saved: #{download_location}"
- end
-
- def download_location
- config[:file] ||= File.join Dir.pwd, "#{@name_args[0]}-#{version}.tar.gz"
- config[:file]
- end
-
- def replacement_cookbook
- File.basename(current_cookbook_data["replacement"])
- end
-
- def specific_cookbook_version_url
- "#{cookbooks_api_url}/#{@name_args[0]}/versions/#{@name_args[1].tr('.', '_')}"
- end
- end
- end
-end
diff --git a/lib/chef/knife/cookbook_site_install.rb b/lib/chef/knife/cookbook_site_install.rb
deleted file mode 100644
index 43d015dcc4..0000000000
--- a/lib/chef/knife/cookbook_site_install.rb
+++ /dev/null
@@ -1,196 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2010-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/knife"
-require "chef/exceptions"
-require "shellwords"
-require "mixlib/archive"
-
-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"
- end
-
- banner "knife cookbook site install COOKBOOK [VERSION] (options)"
- category "cookbook site"
-
- option :no_deps,
- :short => "-D",
- :long => "--skip-dependencies",
- :boolean => true,
- :default => false,
- :description => "Skips automatic dependency installation."
-
- option :cookbook_path,
- :short => "-o PATH:PATH",
- :long => "--cookbook-path PATH:PATH",
- :description => "A colon-separated path to look for cookbooks in",
- :proc => lambda { |o| o.split(":") }
-
- option :default_branch,
- :short => "-B BRANCH",
- :long => "--branch BRANCH",
- :description => "Default branch to work with",
- :default => "master"
-
- option :use_current_branch,
- :short => "-b",
- :long => "--use-current-branch",
- :description => "Use the current branch",
- :boolean => true,
- :default => false
-
- option :supermarket_site,
- :short => "-m SUPERMARKET_SITE",
- :long => "--supermarket-site SUPERMARKET_SITE",
- :description => "Supermarket Site",
- :default => "https://supermarket.chef.io",
- :proc => Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket }
-
- attr_reader :cookbook_name
- attr_reader :vendor_path
-
- def run
- extend Chef::Mixin::ShellOut
-
- if config[:cookbook_path]
- Chef::Config[:cookbook_path] = config[:cookbook_path]
- else
- config[:cookbook_path] = Chef::Config[:cookbook_path]
- end
-
- @cookbook_name = parse_name_args!
- # Check to ensure we have a valid source of cookbooks before continuing
- #
- @install_path = File.expand_path(Array(config[:cookbook_path]).first)
- ui.info "Installing #@cookbook_name to #{@install_path}"
-
- @repo = CookbookSCMRepo.new(@install_path, ui, config)
- #cookbook_path = File.join(vendor_path, name_args[0])
- upstream_file = File.join(@install_path, "#{@cookbook_name}.tar.gz")
-
- @repo.sanity_check
- unless config[:use_current_branch]
- @repo.reset_to_default_state
- @repo.prepare_to_import(@cookbook_name)
- end
-
- downloader = download_cookbook_to(upstream_file)
- clear_existing_files(File.join(@install_path, @cookbook_name))
- extract_cookbook(upstream_file, downloader.version)
-
- # 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")
- File.unlink(upstream_file)
-
- if @repo.finalize_updates_to(@cookbook_name, downloader.version)
- unless config[:use_current_branch]
- @repo.reset_to_default_state
- end
- @repo.merge_updates_from(@cookbook_name, downloader.version)
- else
- unless config[:use_current_branch]
- @repo.reset_to_default_state
- end
- end
-
- unless config[:no_deps]
- preferred_metadata.dependencies.each do |cookbook, version_list|
- # Doesn't do versions.. yet
- nv = self.class.new
- nv.config = config
- nv.name_args = [ cookbook ]
- nv.run
- end
- end
- end
-
- def parse_name_args!
- if name_args.empty?
- ui.error("Please specify a cookbook to download and install.")
- exit 1
- elsif name_args.size >= 2
- unless name_args.last.match(/^(\d+)(\.\d+){1,2}$/) && name_args.size == 2
- ui.error("Installing multiple cookbooks at once is not supported.")
- exit 1
- end
- end
- name_args.first
- end
-
- def download_cookbook_to(download_path)
- downloader = Chef::Knife::CookbookSiteDownload.new
- downloader.config[:file] = download_path
- downloader.config[:supermarket_site] = config[:supermarket_site]
- downloader.name_args = name_args
- downloader.run
- downloader
- end
-
- def extract_cookbook(upstream_file, version)
- ui.info("Uncompressing #{@cookbook_name} version #{version}.")
- Mixlib::Archive.new(convert_path(upstream_file)).extract(@install_path, perms: false)
- end
-
- def clear_existing_files(cookbook_path)
- ui.info("Removing pre-existing version.")
- FileUtils.rmtree(cookbook_path) if File.directory?(cookbook_path)
- end
-
- def convert_path(upstream_file)
- # converts a Windows path (C:\foo) to a mingw path (/c/foo)
- if ENV["MSYSTEM"] == "MINGW32"
- return upstream_file.sub(/^([[:alpha:]]):/, '/\1')
- else
- return Shellwords.escape upstream_file
- end
- end
-
- # Get the preferred metadata path on disk. Chef prefers the metadata.rb
- # over the metadata.json.
- #
- # @raise if there is no metadata in the cookbook
- #
- # @return [Chef::Cookbook::Metadata]
- def preferred_metadata
- md = Chef::Cookbook::Metadata.new
-
- rb = File.join(@install_path, @cookbook_name, "metadata.rb")
- if File.exist?(rb)
- md.from_file(rb)
- return md
- end
-
- json = File.join(@install_path, @cookbook_name, "metadata.json")
- if File.exist?(json)
- json = IO.read(json)
- md.from_json(json)
- return md
- end
-
- raise Chef::Exceptions::MetadataNotFound.new(@install_path, @cookbook_name)
- end
- end
- end
-end
diff --git a/lib/chef/knife/cookbook_site_list.rb b/lib/chef/knife/cookbook_site_list.rb
deleted file mode 100644
index 3bdef8abe5..0000000000
--- a/lib/chef/knife/cookbook_site_list.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-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/knife"
-
-class Chef
- class Knife
- class CookbookSiteList < Knife
-
- banner "knife cookbook site list (options)"
- category "cookbook site"
-
- option :with_uri,
- :short => "-w",
- :long => "--with-uri",
- :description => "Show corresponding URIs"
-
- option :supermarket_site,
- :short => "-m SUPERMARKET_SITE",
- :long => "--supermarket-site SUPERMARKET_SITE",
- :description => "Supermarket Site",
- :default => "https://supermarket.chef.io",
- :proc => Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket }
-
- def run
- if config[:with_uri]
- cookbooks = Hash.new
- 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))
- end
- end
-
- def get_cookbook_list(items = 10, start = 0, cookbook_collection = {})
- cookbooks_url = "#{config[:supermarket_site]}/api/v1/cookbooks?items=#{items}&start=#{start}"
- cr = noauth_rest.get(cookbooks_url)
- cr["items"].each do |cookbook|
- cookbook_collection[cookbook["cookbook_name"]] = cookbook
- end
- new_start = start + cr["items"].length
- if new_start < cr["total"]
- get_cookbook_list(items, new_start, cookbook_collection)
- else
- cookbook_collection
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/cookbook_site_search.rb b/lib/chef/knife/cookbook_site_search.rb
deleted file mode 100644
index d401844217..0000000000
--- a/lib/chef/knife/cookbook_site_search.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-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/knife"
-
-class Chef
- class Knife
- class CookbookSiteSearch < Knife
-
- banner "knife cookbook site search QUERY (options)"
- category "cookbook site"
-
- option :supermarket_site,
- :short => "-m SUPERMARKET_SITE",
- :long => "--supermarket-site SUPERMARKET_SITE",
- :description => "Supermarket Site",
- :default => "https://supermarket.chef.io",
- :proc => Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket }
-
- def run
- output(search_cookbook(name_args[0]))
- end
-
- def search_cookbook(query, items = 10, start = 0, cookbook_collection = {})
- cookbooks_url = "#{config[:supermarket_site]}/api/v1/search?q=#{query}&items=#{items}&start=#{start}"
- cr = noauth_rest.get(cookbooks_url)
- cr["items"].each do |cookbook|
- cookbook_collection[cookbook["cookbook_name"]] = cookbook
- end
- new_start = start + cr["items"].length
- if new_start < cr["total"]
- search_cookbook(query, items, new_start, cookbook_collection)
- else
- cookbook_collection
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/cookbook_site_share.rb b/lib/chef/knife/cookbook_site_share.rb
deleted file mode 100644
index d55d6c123a..0000000000
--- a/lib/chef/knife/cookbook_site_share.rb
+++ /dev/null
@@ -1,170 +0,0 @@
-# Author:: Nuo Yan (<nuo@chef.io>)
-# Author:: Tim Hinderliter (<tim@chef.io>)
-# Copyright:: Copyright 2010-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/knife"
-require "chef/mixin/shell_out"
-
-class Chef
- class Knife
- class CookbookSiteShare < Knife
-
- include Chef::Mixin::ShellOut
-
- deps do
- require "chef/cookbook_loader"
- require "chef/cookbook_uploader"
- require "chef/cookbook_site_streaming_uploader"
- require "mixlib/shellout"
- end
-
- include Chef::Mixin::ShellOut
-
- banner "knife cookbook site share COOKBOOK [CATEGORY] (options)"
- category "cookbook site"
-
- option :cookbook_path,
- :short => "-o PATH:PATH",
- :long => "--cookbook-path PATH:PATH",
- :description => "A colon-separated path to look for cookbooks in",
- :proc => lambda { |o| Chef::Config.cookbook_path = o.split(":") }
-
- option :dry_run,
- :long => "--dry-run",
- :short => "-n",
- :boolean => true,
- :default => false,
- :description => "Don't take action, only print what files will be uploaded to Supermarket."
-
- option :supermarket_site,
- :short => "-m SUPERMARKET_SITE",
- :long => "--supermarket-site SUPERMARKET_SITE",
- :description => "Supermarket Site",
- :default => "https://supermarket.chef.io",
- :proc => Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket }
-
- def run
- config[:cookbook_path] ||= Chef::Config[:cookbook_path]
-
- if @name_args.length < 1
- show_usage
- ui.fatal("You must specify the cookbook name.")
- exit(1)
- elsif @name_args.length < 2
- cookbook_name = @name_args[0]
- category = get_category(cookbook_name)
- else
- cookbook_name = @name_args[0]
- category = @name_args[1]
- end
-
- cl = Chef::CookbookLoader.new(config[:cookbook_path])
- if cl.cookbook_exists?(cookbook_name)
- cookbook = cl[cookbook_name]
- Chef::CookbookUploader.new(cookbook).validate_cookbooks
- tmp_cookbook_dir = Chef::CookbookSiteStreamingUploader.create_build_dir(cookbook)
- begin
- Chef::Log.debug("Temp cookbook directory is #{tmp_cookbook_dir.inspect}")
- ui.info("Making tarball #{cookbook_name}.tgz")
- shell_out!("#{tar_cmd} -czf #{cookbook_name}.tgz #{cookbook_name}", :cwd => tmp_cookbook_dir)
- rescue => e
- ui.error("Error making tarball #{cookbook_name}.tgz: #{e.message}. Increase log verbosity (-VV) for more information.")
- Chef::Log.debug("\n#{e.backtrace.join("\n")}")
- exit(1)
- end
-
- if config[:dry_run]
- ui.info("Not uploading #{cookbook_name}.tgz due to --dry-run flag.")
- result = shell_out!("#{tar_cmd} -tzf #{cookbook_name}.tgz", :cwd => tmp_cookbook_dir)
- ui.info(result.stdout)
- FileUtils.rm_rf tmp_cookbook_dir
- return
- end
-
- begin
- do_upload("#{tmp_cookbook_dir}/#{cookbook_name}.tgz", category, Chef::Config[:node_name], Chef::Config[:client_key])
- ui.info("Upload complete")
- 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 Supermarket: #{e.message}. Increase log verbosity (-VV) for more information.")
- Chef::Log.debug("\n#{e.backtrace.join("\n")}")
- exit(1)
- end
-
- else
- ui.error("Could not find cookbook #{cookbook_name} in your cookbook path.")
- exit(1)
- end
- end
-
- def get_category(cookbook_name)
- data = noauth_rest.get("#{config[:supermarket_site]}/api/v1/cookbooks/#{@name_args[0]}")
- data["category"]
- rescue => e
- return "Other" if e.kind_of?(Net::HTTPServerException) && e.response.code == "404"
- 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
-
- def do_upload(cookbook_filename, cookbook_category, user_id, user_secret_filename)
- uri = "#{config[:supermarket_site]}/api/v1/cookbooks"
-
- 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,
- })
-
- 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 Supermarket."
- exit(1)
- else
- ui.error "#{res['error_messages'][0]}"
- exit(1)
- end
- else
- ui.error "Unknown error while sharing cookbook"
- ui.error "Server response: #{http_resp.body}"
- exit(1)
- end
- end
- res
- end
-
- def tar_cmd
- if !@tar_cmd
- @tar_cmd = "tar"
- begin
- # Unix and Mac only - prefer gnutar
- if shell_out("which gnutar").exitstatus.equal?(0)
- @tar_cmd = "gnutar"
- end
- rescue Errno::ENOENT
- end
- end
- @tar_cmd
- end
- end
-
- end
-end
diff --git a/lib/chef/knife/cookbook_site_show.rb b/lib/chef/knife/cookbook_site_show.rb
deleted file mode 100644
index ce153ca5a1..0000000000
--- a/lib/chef/knife/cookbook_site_show.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-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/knife"
-
-class Chef
- class Knife
- class CookbookSiteShow < Knife
-
- banner "knife cookbook site show COOKBOOK [VERSION] (options)"
- category "cookbook site"
-
- option :supermarket_site,
- :short => "-m SUPERMARKET_SITE",
- :long => "--supermarket-site SUPERMARKET_SITE",
- :description => "Supermarket Site",
- :default => "https://supermarket.chef.io",
- :proc => Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket }
-
- def run
- output(format_for_display(get_cookbook_data))
- end
-
- def supermarket_uri
- "#{config[:supermarket_site]}/api/v1"
- end
-
- def get_cookbook_data
- case @name_args.length
- when 1
- noauth_rest.get("#{supermarket_uri}/cookbooks/#{@name_args[0]}")
- when 2
- noauth_rest.get("#{supermarket_uri}/cookbooks/#{@name_args[0]}/versions/#{name_args[1].tr('.', '_')}")
- end
- end
-
- def get_cookbook_list(items = 10, start = 0, cookbook_collection = {})
- cookbooks_url = "#{supermarket_uri}/cookbooks?items=#{items}&start=#{start}"
- cr = noauth_rest.get(cookbooks_url)
- cr["items"].each do |cookbook|
- cookbook_collection[cookbook["cookbook_name"]] = cookbook
- end
- new_start = start + cr["items"].length
- if new_start < cr["total"]
- get_cookbook_list(items, new_start, cookbook_collection)
- else
- cookbook_collection
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/cookbook_site_unshare.rb b/lib/chef/knife/cookbook_site_unshare.rb
deleted file mode 100644
index bdabff0b94..0000000000
--- a/lib/chef/knife/cookbook_site_unshare.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-#
-# Author:: Stephen Delano (<stephen@chef.io>)
-# Author:: Tim Hinderliter (<tim@chef.io>)
-# Copyright:: Copyright 2010-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/knife"
-
-class Chef
- class Knife
- class CookbookSiteUnshare < Knife
-
- deps do
- require "chef/json_compat"
- end
-
- banner "knife cookbook site unshare COOKBOOK"
- category "cookbook site"
-
- option :supermarket_site,
- :short => "-m SUPERMARKET_SITE",
- :long => "--supermarket-site SUPERMARKET_SITE",
- :description => "Supermarket Site",
- :default => "https://supermarket.chef.io",
- :proc => Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket }
-
- def run
- @cookbook_name = @name_args[0]
- if @cookbook_name.nil?
- show_usage
- ui.fatal "You must provide the name of the cookbook to unshare"
- exit 1
- end
-
- confirm "Do you really want to unshare all versions of the cookbook #{@cookbook_name}"
-
- begin
- rest.delete "#{config[:supermarket_site]}/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 all versions of the cookbook #{@cookbook_name}"
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/cookbook_site_vendor.rb b/lib/chef/knife/cookbook_site_vendor.rb
deleted file mode 100644
index 291715cc0b..0000000000
--- a/lib/chef/knife/cookbook_site_vendor.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2011-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/knife"
-require "chef/knife/cookbook_site_install"
-
-class Chef::Knife::CookbookSiteVendor < Chef::Knife::CookbookSiteInstall
-
- def self.load_deps
- superclass.load_deps
- end
-
- def self.options=(new_opts)
- superclass.options = new_opts
- end
-
- def self.options
- superclass.options
- end
-
- banner(<<-B)
-*************************************************
-DEPRECATED: please use knife cookbook site install
-*************************************************
-
-#{superclass.banner}
-B
-
- category "deprecated"
-
-end
diff --git a/lib/chef/knife/cookbook_test.rb b/lib/chef/knife/cookbook_test.rb
deleted file mode 100644
index 1a5c9df74e..0000000000
--- a/lib/chef/knife/cookbook_test.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-#
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Matthew Kent (<mkent@magoazul.com>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
-# Copyright:: Copyright 2010-2016, Matthew Kent
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-require "chef/knife"
-
-class Chef
- class Knife
- class CookbookTest < Knife
-
- deps do
- require "chef/cookbook_loader"
- require "chef/cookbook/syntax_check"
- end
-
- banner "knife cookbook test [COOKBOOKS...] (options)"
-
- option :cookbook_path,
- :short => "-o PATH:PATH",
- :long => "--cookbook-path PATH:PATH",
- :description => "A colon-separated path to look for cookbooks in",
- :proc => lambda { |o| o.split(":") }
-
- option :all,
- :short => "-a",
- :long => "--all",
- :description => "Test all cookbooks, rather than just a single cookbook"
-
- def run
- ui.warn("DEPRECATED: Please use ChefSpec or Rubocop to syntax-check cookbooks.")
- config[:cookbook_path] ||= Chef::Config[:cookbook_path]
-
- checked_a_cookbook = false
- if config[:all]
- cl = cookbook_loader
- cl.load_cookbooks
- cl.each do |key, cookbook|
- checked_a_cookbook = true
- test_cookbook(key)
- end
- else
- @name_args.each do |cb|
- ui.info "checking #{cb}"
- next unless cookbook_loader.cookbook_exists?(cb)
- checked_a_cookbook = true
- test_cookbook(cb)
- end
- end
- unless checked_a_cookbook
- ui.warn("No cookbooks to test in #{Array(config[:cookbook_path]).join(',')} - is your cookbook path misconfigured?")
- end
- end
-
- def test_cookbook(cookbook)
- ui.info("Running syntax check on #{cookbook}")
- Array(config[:cookbook_path]).reverse_each do |path|
- syntax_checker = Chef::Cookbook::SyntaxCheck.for_cookbook(cookbook, path)
- test_ruby(syntax_checker)
- test_templates(syntax_checker)
- end
- end
-
- def test_ruby(syntax_checker)
- ui.info("Validating ruby files")
- exit(1) unless syntax_checker.validate_ruby_files
- end
-
- def test_templates(syntax_checker)
- ui.info("Validating templates")
- exit(1) unless syntax_checker.validate_templates
- end
-
- def cookbook_loader
- @cookbook_loader ||= Chef::CookbookLoader.new(config[:cookbook_path])
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/cookbook_upload.rb b/lib/chef/knife/cookbook_upload.rb
index 6938ac280d..9f6f3c4cb2 100644
--- a/lib/chef/knife/cookbook_upload.rb
+++ b/lib/chef/knife/cookbook_upload.rb
@@ -2,7 +2,7 @@
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
# Author:: Nuo Yan (<yan.nuo@gmail.com>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,82 +18,70 @@
# limitations under the License.
#
-require "chef/knife"
-require "chef/cookbook_uploader"
+require_relative "../knife"
class Chef
class Knife
class CookbookUpload < Knife
-
- CHECKSUM = "checksum"
- MATCH_CHECKSUM = /[0-9a-f]{32,}/
-
deps do
- require "chef/exceptions"
- require "chef/cookbook_loader"
- require "chef/cookbook_uploader"
+ require_relative "../mixin/file_class"
+ include Chef::Mixin::FileClass
+ require_relative "../exceptions"
+ require_relative "../cookbook_loader"
+ require_relative "../cookbook_uploader"
end
banner "knife cookbook upload [COOKBOOKS...] (options)"
option :cookbook_path,
- :short => "-o PATH:PATH",
- :long => "--cookbook-path PATH:PATH",
- :description => "A colon-separated path to look for cookbooks in",
- :proc => lambda { |o| o.split(":") }
+ short: "-o 'PATH:PATH'",
+ long: "--cookbook-path 'PATH:PATH'",
+ description: "A delimited path to search for cookbooks. On Unix the delimiter is ':', on Windows it is ';'.",
+ proc: lambda { |o| o.split(File::PATH_SEPARATOR) }
option :freeze,
- :long => "--freeze",
- :description => "Freeze this version of the cookbook so that it cannot be overwritten",
- :boolean => true
+ long: "--freeze",
+ description: "Freeze this version of the cookbook so that it cannot be overwritten.",
+ boolean: true
option :all,
- :short => "-a",
- :long => "--all",
- :description => "Upload all cookbooks, rather than just a single cookbook"
+ short: "-a",
+ long: "--all",
+ description: "Upload all cookbooks, rather than just a single cookbook."
option :force,
- :long => "--force",
- :boolean => true,
- :description => "Update cookbook versions even if they have been frozen"
+ long: "--force",
+ boolean: true,
+ description: "Update cookbook versions even if they have been frozen."
option :concurrency,
- :long => "--concurrency NUMBER_OF_THREADS",
- :description => "How many concurrent threads will be used",
- :default => 10,
- :proc => lambda { |o| o.to_i }
+ 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",
- :description => "Set ENVIRONMENT's version dependency match the version you're uploading.",
- :default => nil
+ short: "-E",
+ long: "--environment ENVIRONMENT",
+ description: "Set ENVIRONMENT's version dependency match the version you're uploading.",
+ default: nil
option :depends,
- :short => "-d",
- :long => "--include-dependencies",
- :description => "Also upload cookbook dependencies"
+ short: "-d",
+ long: "--include-dependencies",
+ description: "Also upload cookbook dependencies."
def run
# Sanity check before we load anything from the server
- unless config[:all]
- if @name_args.empty?
- show_usage
- ui.fatal("You must specify the --all flag or at least one cookbook name")
- exit 1
- end
- end
-
- config[:cookbook_path] ||= Chef::Config[:cookbook_path]
-
- if @name_args.empty? && ! config[:all]
+ if ! config[:all] && @name_args.empty?
show_usage
ui.fatal("You must specify the --all flag or at least one cookbook name")
exit 1
end
+ config[:cookbook_path] ||= Chef::Config[:cookbook_path]
+
assert_environment_valid!
- warn_about_cookbook_shadowing
version_constraints_to_update = {}
upload_failures = 0
upload_ok = 0
@@ -101,84 +89,100 @@ class Chef
# Get a list of cookbooks and their versions from the server
# to check for the existence of a cookbook's dependencies.
@server_side_cookbooks = Chef::CookbookVersion.list_all_versions
- justify_width = @server_side_cookbooks.map { |name| name.size }.max.to_i + 2
- if config[:all]
- cookbook_repo.load_cookbooks_without_shadow_warning
- cookbooks_for_upload = []
- cookbook_repo.each do |cookbook_name, cookbook|
- cookbooks_for_upload << cookbook
- cookbook.freeze_version if config[:freeze]
- version_constraints_to_update[cookbook_name] = cookbook.version
- end
- if cookbooks_for_upload.any?
- begin
- upload(cookbooks_for_upload, justify_width)
- rescue Exceptions::CookbookFrozen
- ui.warn("Not updating version constraints for some cookbooks in the environment as the cookbook is frozen.")
- end
- ui.info("Uploaded all cookbooks.")
- else
- 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
- if @name_args.empty?
- show_usage
- ui.error("You must specify the --all flag or at least one cookbook name")
- exit 1
+ justify_width = @server_side_cookbooks.map(&:size).max.to_i + 2
+
+ cookbooks = []
+ cookbooks_to_upload.each do |cookbook_name, cookbook|
+ raise Chef::Exceptions::MetadataNotFound.new(cookbook.root_paths[0], cookbook_name) unless cookbook.has_metadata_file?
+
+ if cookbook.metadata.name.nil?
+ message = "Cookbook loaded at path [#{cookbook.root_paths[0]}] has invalid metadata: #{cookbook.metadata.errors.join("; ")}"
+ raise Chef::Exceptions::MetadataNotValid, message
end
- cookbooks_to_upload.each do |cookbook_name, cookbook|
- cookbook.freeze_version if config[:freeze]
- begin
- upload([cookbook], justify_width)
- upload_ok += 1
+ cookbooks << cookbook
+ end
+
+ if cookbooks.empty?
+ 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: '#{File.expand_path(cookbook_path)}'. Use --cookbook-path to specify the desired path.")
+ else
+ Chef::CookbookLoader.copy_to_tmp_dir_from_array(cookbooks) do |tmp_cl|
+ tmp_cl.load_cookbooks
+ tmp_cl.compile_metadata
+ tmp_cl.freeze_versions if config[:freeze]
+
+ cookbooks_for_upload = []
+ tmp_cl.each do |cookbook_name, cookbook|
+ cookbooks_for_upload << cookbook
version_constraints_to_update[cookbook_name] = cookbook.version
- rescue Exceptions::CookbookNotFoundInRepo => e
- upload_failures += 1
- ui.error("Could not find cookbook #{cookbook_name} in your cookbook path, skipping it")
- Log.debug(e)
- upload_failures += 1
- rescue Exceptions::CookbookFrozen
- ui.warn("Not updating version constraints for #{cookbook_name} in the environment as the cookbook is frozen.")
- upload_failures += 1
end
- end
+ if config[:all]
+ if cookbooks_for_upload.any?
+ begin
+ upload(cookbooks_for_upload, justify_width)
+ rescue Chef::Exceptions::CookbookFrozen
+ ui.warn("Not updating version constraints for some cookbooks in the environment as the cookbook is frozen.")
+ ui.error("Uploading of some of the cookbooks must be failed. Remove cookbook whose version is frozen from your cookbooks repo OR use --force option.")
+ upload_failures += 1
+ rescue SystemExit => e
+ raise exit e.status
+ end
+ ui.info("Uploaded all cookbooks.") if upload_failures == 0
+ end
+ else
+ tmp_cl.each do |cookbook_name, cookbook|
- if upload_failures == 0
- ui.info "Uploaded #{upload_ok} cookbook#{upload_ok > 1 ? "s" : ""}."
- elsif upload_failures > 0 && upload_ok > 0
- ui.warn "Uploaded #{upload_ok} cookbook#{upload_ok > 1 ? "s" : ""} ok but #{upload_failures} " +
- "cookbook#{upload_failures > 1 ? "s" : ""} upload failed."
- elsif upload_failures > 0 && upload_ok == 0
- ui.error "Failed to upload #{upload_failures} cookbook#{upload_failures > 1 ? "s" : ""}."
- exit 1
- end
- end
+ upload([cookbook], justify_width)
+ upload_ok += 1
+ rescue Exceptions::CookbookNotFoundInRepo => e
+ upload_failures += 1
+ ui.error("Could not find cookbook #{cookbook_name} in your cookbook path, skipping it")
+ Log.debug(e)
+ upload_failures += 1
+ rescue Exceptions::CookbookFrozen
+ ui.warn("Not updating version constraints for #{cookbook_name} in the environment as the cookbook is frozen.")
+ upload_failures += 1
+ rescue SystemExit => e
+ raise exit e.status
- unless version_constraints_to_update.empty?
- update_version_constraints(version_constraints_to_update) if config[:environment]
+ end
+
+ if upload_failures == 0
+ ui.info "Uploaded #{upload_ok} cookbook#{upload_ok == 1 ? "" : "s"}."
+ elsif upload_failures > 0 && upload_ok > 0
+ ui.warn "Uploaded #{upload_ok} cookbook#{upload_ok == 1 ? "" : "s"} ok but #{upload_failures} " +
+ "cookbook#{upload_failures == 1 ? "" : "s"} upload failed."
+ elsif upload_failures > 0 && upload_ok == 0
+ ui.error "Failed to upload #{upload_failures} cookbook#{upload_failures == 1 ? "" : "s"}."
+ exit 1
+ end
+ end
+ unless version_constraints_to_update.empty?
+ update_version_constraints(version_constraints_to_update) if config[:environment]
+ end
+ end
end
end
def cookbooks_to_upload
@cookbooks_to_upload ||=
if config[:all]
- cookbook_repo.load_cookbooks_without_shadow_warning
+ cookbook_repo.load_cookbooks
else
upload_set = {}
@name_args.each do |cookbook_name|
- begin
- if ! upload_set.has_key?(cookbook_name)
- upload_set[cookbook_name] = cookbook_repo[cookbook_name]
- if config[:depends]
- upload_set[cookbook_name].metadata.dependencies.each { |dep, ver| @name_args << dep }
- end
+
+ unless upload_set.key?(cookbook_name)
+ upload_set[cookbook_name] = cookbook_repo[cookbook_name]
+ if config[:depends]
+ upload_set[cookbook_name].metadata.dependencies.each_key { |dep| @name_args << dep }
end
- rescue Exceptions::CookbookNotFoundInRepo => e
- ui.error("Could not find cookbook #{cookbook_name} in your cookbook path, skipping it")
- Log.debug(e)
end
+ rescue Exceptions::CookbookNotFoundInRepo => e
+ ui.error(e.message)
+ Log.debug(e)
+
end
upload_set
end
@@ -202,31 +206,11 @@ class Chef
@environment ||= config[:environment] ? Environment.load(config[:environment]) : nil
end
- def warn_about_cookbook_shadowing
- # because cookbooks are lazy-loaded, we have to force the loader
- # to load the cookbooks the user intends to upload here:
- cookbooks_to_upload
-
- unless cookbook_repo.merged_cookbooks.empty?
- ui.warn "* " * 40
- ui.warn(<<-WARNING)
-The cookbooks: #{cookbook_repo.merged_cookbooks.join(', ')} exist in multiple places in your cookbook_path.
-A composite version of these cookbooks has been compiled for uploading.
-
-#{ui.color('IMPORTANT:', :red, :bold)} In a future version of Chef, this behavior will be removed and you will no longer
-be able to have the same version of a cookbook in multiple places in your cookbook_path.
-WARNING
- ui.warn "The affected cookbooks are located:"
- ui.output ui.format_for_display(cookbook_repo.merged_cookbook_paths)
- ui.warn "* " * 40
- end
- end
-
private
def assert_environment_valid!
environment
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => e
if e.response.code.to_s == "404"
ui.error "The environment #{config[:environment]} does not exist on the server, aborting."
Log.debug(e)
@@ -242,7 +226,7 @@ WARNING
check_for_broken_links!(cb)
check_for_dependencies!(cb)
end
- Chef::CookbookUploader.new(cookbooks, :force => config[:force], :concurrency => config[:concurrency]).upload_cookbooks
+ Chef::CookbookUploader.new(cookbooks, force: config[:force], concurrency: config[:concurrency]).upload_cookbooks
rescue Chef::Exceptions::CookbookFrozen => e
ui.error e
raise
@@ -253,13 +237,13 @@ WARNING
# manifest object, but the manifest becomes invalid when you
# regenerate the metadata
broken_files = cookbook.dup.manifest_records_by_path.select do |path, info|
- info[CHECKSUM].nil? || info[CHECKSUM] !~ MATCH_CHECKSUM
+ !/[0-9a-f]{32,}/.match?(info["checksum"])
end
unless broken_files.empty?
broken_filenames = Array(broken_files).map { |path, info| path }
ui.error "The cookbook #{cookbook.name} has one or more broken files"
ui.error "This is probably caused by broken symlinks in the cookbook directory"
- ui.error "The broken file(s) are: #{broken_filenames.join(' ')}"
+ ui.error "The broken file(s) are: #{broken_filenames.join(" ")}"
exit 1
end
end
@@ -275,7 +259,7 @@ WARNING
missing_cookbook_names = missing_dependencies.map { |cookbook_name, version| "'#{cookbook_name}' version '#{version}'" }
ui.error "Cookbook #{cookbook.name} depends on cookbooks which are not currently"
ui.error "being uploaded and cannot be found on the server."
- ui.error "The missing cookbook(s) are: #{missing_cookbook_names.join(', ')}"
+ ui.error "The missing cookbook(s) are: #{missing_cookbook_names.join(", ")}"
exit 1
end
end
@@ -288,7 +272,7 @@ WARNING
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"])
- Log.debug "Matched cookbook '#{cookbook_name}' with constraint '#{version}' to cookbook version '#{versions_hash['version']}' on the server"
+ Log.debug "Matched cookbook '#{cookbook_name}' with constraint '#{version}' to cookbook version '#{versions_hash["version"]}' on the server"
return true
end
end
diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb
index b2670f196b..9aa81da82f 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@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,10 @@
# limitations under the License.
#
-require "chef/run_list"
-require "chef/util/path_helper"
-require "pathname"
+require_relative "../../run_list"
+require_relative "../../util/path_helper"
+require "pathname" unless defined?(Pathname)
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Knife
@@ -27,11 +28,13 @@ class Chef
# bootstrap templates. For backwards compatibility, they +must+ set the
# following instance variables:
# * @config - a hash of knife's config values
- # * @run_list - the run list for the node to boostrap
+ # * @run_list - the run list for the node to bootstrap
#
class BootstrapContext
attr_accessor :client_pem
+ attr_accessor :config
+ attr_accessor :chef_config
def initialize(config, run_list, chef_config, secret = nil)
@config = config
@@ -41,13 +44,13 @@ class Chef
end
def bootstrap_environment
- @config[:environment]
+ config[:environment]
end
def validation_key
- if @chef_config.has_key?(:validation_key) &&
- File.exist?(File.expand_path(@chef_config[:validation_key]))
- IO.read(File.expand_path(@chef_config[:validation_key]))
+ if chef_config[:validation_key] &&
+ File.exist?(File.expand_path(chef_config[:validation_key]))
+ IO.read(File.expand_path(chef_config[:validation_key]))
else
false
end
@@ -62,40 +65,68 @@ class Chef
end
# Contains commands and content, see trusted_certs_content
- # TODO: Rename to trusted_certs_script
+ # @todo Rename to trusted_certs_script
def trusted_certs
@trusted_certs ||= trusted_certs_content
end
+ def get_log_location
+ if !(chef_config[:config_log_location].class == IO ) && (chef_config[:config_log_location].nil? || chef_config[:config_log_location].to_s.empty?)
+ "STDOUT"
+ elsif chef_config[:config_log_location].equal?(:win_evt)
+ raise "The value :win_evt is not supported for config_log_location on Linux Platforms \n"
+ elsif chef_config[:config_log_location].equal?(:syslog)
+ ":syslog"
+ elsif chef_config[:config_log_location].equal?(STDOUT)
+ "STDOUT"
+ elsif chef_config[:config_log_location].equal?(STDERR)
+ "STDERR"
+ elsif chef_config[:config_log_location]
+ %Q{"#{chef_config[:config_log_location]}"}
+ else
+ "STDOUT"
+ end
+ end
+
def config_content
- client_rb = <<-CONFIG
-log_location STDOUT
-chef_server_url "#{@chef_config[:chef_server_url]}"
-validation_client_name "#{@chef_config[:validation_client_name]}"
+ client_rb = <<~CONFIG
+ chef_server_url "#{chef_config[:chef_server_url]}"
+ validation_client_name "#{chef_config[:validation_client_name]}"
CONFIG
- if @config[:chef_node_name]
- client_rb << %Q{node_name "#{@config[:chef_node_name]}"\n}
+
+ unless chef_config[:chef_license].nil?
+ client_rb << "chef_license \"#{chef_config[:chef_license]}\"\n"
+ end
+
+ unless chef_config[:config_log_level].nil? || chef_config[:config_log_level].empty?
+ client_rb << %Q{log_level :#{chef_config[:config_log_level]}\n}
+ end
+
+ client_rb << "log_location #{get_log_location}\n"
+
+ if config[:chef_node_name]
+ client_rb << %Q{node_name "#{config[:chef_node_name]}"\n}
else
client_rb << "# Using default node name (fqdn)\n"
end
# We configure :verify_api_cert only when it's overridden on the CLI
# or when specified in the knife config.
- if !@config[:node_verify_api_cert].nil? || knife_config.has_key?(:verify_api_cert)
- value = @config[:node_verify_api_cert].nil? ? knife_config[:verify_api_cert] : @config[:node_verify_api_cert]
+ if !config[:node_verify_api_cert].nil? || config.key?(:verify_api_cert)
+ value = config[:node_verify_api_cert].nil? ? config[:verify_api_cert] : config[:node_verify_api_cert]
client_rb << %Q{verify_api_cert #{value}\n}
end
# We configure :ssl_verify_mode only when it's overridden on the CLI
# 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]
+ if config[:node_ssl_verify_mode] || config.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]
+ config[:ssl_verify_mode]
else
nil
end
@@ -105,27 +136,27 @@ validation_client_name "#{@chef_config[:validation_client_name]}"
end
end
- if @config[:ssl_verify_mode]
- client_rb << %Q{ssl_verify_mode :#{knife_config[:ssl_verify_mode]}\n}
+ if config[:ssl_verify_mode]
+ client_rb << %Q{ssl_verify_mode :#{config[:ssl_verify_mode]}\n}
end
- if knife_config[:bootstrap_proxy]
- client_rb << %Q{http_proxy "#{knife_config[:bootstrap_proxy]}"\n}
- client_rb << %Q{https_proxy "#{knife_config[:bootstrap_proxy]}"\n}
+ if config[:bootstrap_proxy]
+ client_rb << %Q{http_proxy "#{config[:bootstrap_proxy]}"\n}
+ client_rb << %Q{https_proxy "#{config[:bootstrap_proxy]}"\n}
end
- if knife_config[:bootstrap_proxy_user]
- client_rb << %Q{http_proxy_user "#{knife_config[:bootstrap_proxy_user]}"\n}
- client_rb << %Q{https_proxy_user "#{knife_config[:bootstrap_proxy_user]}"\n}
+ if config[:bootstrap_proxy_user]
+ client_rb << %Q{http_proxy_user "#{config[:bootstrap_proxy_user]}"\n}
+ client_rb << %Q{https_proxy_user "#{config[:bootstrap_proxy_user]}"\n}
end
- if knife_config[:bootstrap_proxy_pass]
- client_rb << %Q{http_proxy_pass "#{knife_config[:bootstrap_proxy_pass]}"\n}
- client_rb << %Q{https_proxy_pass "#{knife_config[:bootstrap_proxy_pass]}"\n}
+ if config[:bootstrap_proxy_pass]
+ client_rb << %Q{http_proxy_pass "#{config[:bootstrap_proxy_pass]}"\n}
+ client_rb << %Q{https_proxy_pass "#{config[:bootstrap_proxy_pass]}"\n}
end
- if knife_config[:bootstrap_no_proxy]
- client_rb << %Q{no_proxy "#{knife_config[:bootstrap_no_proxy]}"\n}
+ if config[:bootstrap_no_proxy]
+ client_rb << %Q{no_proxy "#{config[:bootstrap_no_proxy]}"\n}
end
if encrypted_data_bag_secret
@@ -136,14 +167,16 @@ validation_client_name "#{@chef_config[:validation_client_name]}"
client_rb << %Q{trusted_certs_dir "/etc/chef/trusted_certs"\n}
end
- if Chef::Config[:fips]
- client_rb << <<-CONFIG
-fips true
-chef_version = ::Chef::VERSION.split(".")
-unless chef_version[0].to_i > 12 || (chef_version[0].to_i == 12 && chef_version[1].to_i >= 8)
- raise "FIPS Mode requested but not supported by this client"
-end
-CONFIG
+ if chef_config[:fips]
+ client_rb << "fips true\n"
+ end
+
+ unless chef_config[:file_cache_path].nil?
+ client_rb << "file_cache_path \"#{chef_config[:file_cache_path]}\"\n"
+ end
+
+ unless chef_config[:file_backup_path].nil?
+ client_rb << "file_backup_path \"#{chef_config[:file_backup_path]}\"\n"
end
client_rb
@@ -151,54 +184,42 @@ 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] || ChefUtils::Dist::Infra::CLIENT
s = "#{client_path} -j /etc/chef/first-boot.json"
- s << " -l debug" if @config[:verbosity] && @config[:verbosity] >= 2
+ if config[:verbosity] && config[:verbosity] >= 3
+ s << " -l trace"
+ elsif config[:verbosity] && config[:verbosity] >= 2
+ s << " -l debug"
+ end
s << " -E #{bootstrap_environment}" unless bootstrap_environment.nil?
- s << " --no-color" unless @config[:color]
+ s << " --no-color" unless config[:color]
s
end
- def knife_config
- @chef_config.key?(:knife) ? @chef_config[:knife] : {}
- end
-
#
- # chef version string to fetch the latest current version from omnitruck
- # If user is on X.Y.Z bootstrap will use the latest X release
- # X here can be 10 or 11
- def latest_current_chef_version_string
- installer_version_string = nil
- if @config[:prerelease]
- installer_version_string = ["-p"]
- else
- chef_version_string = if knife_config[:bootstrap_version]
- knife_config[:bootstrap_version]
- else
- Chef::VERSION.split(".").first
- end
-
- installer_version_string = ["-v", chef_version_string]
+ # Returns the version of Chef to install (as recognized by the Omnitruck API)
+ #
+ # @return [String] download version string
+ def version_to_install
+ return config[:bootstrap_version] if config[:bootstrap_version]
- # If bootstrapping a pre-release version add -p to the installer string
- if chef_version_string.split(".").length > 3
- installer_version_string << "-p"
- end
+ if config[:channel] == "stable"
+ Chef::VERSION.split(".").first
+ else
+ "latest"
end
-
- installer_version_string.join(" ")
end
def first_boot
- (@config[:first_boot_attributes] || {}).tap do |attributes|
- if @config[:policy_name] && @config[:policy_group]
- attributes[:policy_name] = @config[:policy_name]
- attributes[:policy_group] = @config[:policy_group]
+ (config[:first_boot_attributes] = Mash.new(config[:first_boot_attributes]) || Mash.new).tap do |attributes|
+ if config[:policy_name] && config[:policy_group]
+ attributes[:policy_name] = config[:policy_name]
+ attributes[:policy_group] = config[:policy_group]
else
attributes[:run_list] = @run_list
end
-
- attributes.merge!(:tags => @config[:tags]) if @config[:tags] && !@config[:tags].empty?
+ attributes.delete(:run_list) if attributes[:policy_name] && !attributes[:policy_name].empty?
+ attributes.merge!(tags: config[:tags]) if config[:tags] && !config[:tags].empty?
end
end
@@ -208,8 +229,8 @@ CONFIG
# This string should contain both the commands necessary to both create the files, as well as their content
def trusted_certs_content
content = ""
- if @chef_config[:trusted_certs_dir]
- Dir.glob(File.join(Chef::Util::PathHelper.escape_glob_dir(@chef_config[:trusted_certs_dir]), "*.{crt,pem}")).each do |cert|
+ if chef_config[:trusted_certs_dir]
+ Dir.glob(File.join(Chef::Util::PathHelper.escape_glob_dir(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"
end
@@ -219,8 +240,8 @@ CONFIG
def client_d_content
content = ""
- if @chef_config[:client_d_dir] && File.exist?(@chef_config[:client_d_dir])
- root = Pathname(@chef_config[:client_d_dir])
+ if chef_config[:client_d_dir] && File.exist?(chef_config[:client_d_dir])
+ root = Pathname(chef_config[:client_d_dir])
root.find do |f|
relative = f.relative_path_from(root)
if f != root
@@ -229,7 +250,7 @@ CONFIG
content << "mkdir #{file_on_node}\n"
else
content << "cat > #{file_on_node} <<'EOP'\n" +
- f.read + "\nEOP\n"
+ f.read.gsub("'", "'\\\\''") + "\nEOP\n"
end
end
end
diff --git a/lib/chef/knife/core/cookbook_scm_repo.rb b/lib/chef/knife/core/cookbook_scm_repo.rb
index e909066b02..ba194a8a6d 100644
--- a/lib/chef/knife/core/cookbook_scm_repo.rb
+++ b/lib/chef/knife/core/cookbook_scm_repo.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,13 +16,13 @@
# limitations under the License.
#
-require "chef/mixin/shell_out"
+require_relative "../../mixin/shell_out"
class Chef
class Knife
class CookbookSCMRepo
- DIRTY_REPO = /^[\s]+M/
+ DIRTY_REPO = /^\s+M/.freeze
include Chef::Mixin::ShellOut
@@ -50,7 +50,7 @@ class Chef
exit 1
end
if use_current_branch
- @default_branch = get_current_branch()
+ @default_branch = get_current_branch
end
unless branch_exists?(default_branch)
ui.error "The default branch '#{default_branch}' does not exist"
@@ -58,7 +58,7 @@ class Chef
exit 1
end
cmd = git("status --porcelain")
- if cmd.stdout =~ DIRTY_REPO
+ if DIRTY_REPO.match?(cmd.stdout)
ui.error "You have uncommitted changes to your cookbook repo (#{repo_path}):"
ui.msg cmd.stdout
ui.info "Commit or stash your changes before importing cookbooks"
@@ -122,7 +122,7 @@ class Chef
git("branch --no-color").stdout.lines.any? { |l| l =~ /\s#{Regexp.escape(branch_name)}(?:\s|$)/ }
end
- def get_current_branch()
+ def get_current_branch
ref = git("symbolic-ref HEAD").stdout
ref.chomp.split("/")[2]
end
@@ -131,9 +131,9 @@ class Chef
def git_repo?(directory)
if File.directory?(File.join(directory, ".git"))
- return true
+ true
elsif File.dirname(directory) == directory
- return false
+ false
else
git_repo?(File.dirname(directory))
end
@@ -151,7 +151,7 @@ class Chef
end
def git(command)
- shell_out!("git #{command}", :cwd => repo_path)
+ shell_out!("git #{command}", cwd: repo_path)
end
end
diff --git a/lib/chef/knife/core/custom_manifest_loader.rb b/lib/chef/knife/core/custom_manifest_loader.rb
deleted file mode 100644
index 9fe51599af..0000000000
--- a/lib/chef/knife/core/custom_manifest_loader.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-# Copyright:: Copyright 2015-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/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/formatting_options.rb b/lib/chef/knife/core/formatting_options.rb
new file mode 100644
index 0000000000..cdee2c5989
--- /dev/null
+++ b/lib/chef/knife/core/formatting_options.rb
@@ -0,0 +1,49 @@
+#
+# Author:: Nicolas DUPEUX (<nicolas.dupeux@arkea.com>)
+# Copyright:: Copyright (c) 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
+ module Core
+
+ # This module may be included into a knife subcommand class to automatically
+ # add configuration options used by the StatusPresenter and NodePresenter.
+ module FormattingOptions
+ # @private
+ # Would prefer to do this in a rational way, but can't be done b/c of
+ # Mixlib::CLI's design :(
+ def self.included(includer)
+ includer.class_eval do
+ option :medium_output,
+ short: "-m",
+ long: "--medium",
+ boolean: true,
+ default: false,
+ description: "Include normal attributes in the output"
+
+ option :long_output,
+ short: "-l",
+ long: "--long",
+ boolean: true,
+ default: false,
+ description: "Include all attributes in the output"
+ end
+ 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
index 34c5c53d75..d058379e71 100644
--- a/lib/chef/knife/core/gem_glob_loader.rb
+++ b/lib/chef/knife/core/gem_glob_loader.rb
@@ -1,6 +1,6 @@
# Author:: Christopher Brown (<cb@chef.io>)
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software, Inc
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,14 +16,14 @@
# limitations under the License.
#
-require "chef/version"
-require "chef/util/path_helper"
+require_relative "../../version"
+require_relative "../../util/path_helper"
class Chef
class Knife
class SubcommandLoader
class GemGlobLoader < Chef::Knife::SubcommandLoader
- MATCHES_CHEF_GEM = %r{/chef-[\d]+\.[\d]+\.[\d]+}
- MATCHES_THIS_CHEF_GEM = %r{/chef-#{Chef::VERSION}(-\w+)?(-\w+)?/}
+ 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
@@ -39,7 +39,7 @@ class Chef
# subcommand loader has been modified to load the plugins by using Kernel.load
# with the absolute path.
def gem_and_builtin_subcommands
- require "rubygems"
+ require "rubygems" unless defined?(Gem)
find_subcommands_via_rubygems
rescue LoadError
find_subcommands_via_dirglob
@@ -47,7 +47,7 @@ class Chef
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_dir(File.expand_path("../../../knife", __FILE__)), "*.rb")]
+ files = Dir[File.join(Chef::Util::PathHelper.escape_glob_dir(File.expand_path("../../knife", __dir__)), "*.rb")]
subcommand_files = {}
files.each do |knife_file|
rel_path = knife_file[/#{CHEF_ROOT}#{Regexp.escape(File::SEPARATOR)}(.*)\.rb/, 1]
@@ -98,7 +98,7 @@ class Chef
files.concat gem_files
files.uniq! if check_load_path
- return files
+ files
end
def latest_gem_specs
@@ -111,14 +111,14 @@ class Chef
def check_spec_for_glob(spec, glob)
dirs = if spec.require_paths.size > 1
- "{#{spec.require_paths.join(',')}}"
+ "{#{spec.require_paths.join(",")}}"
else
spec.require_paths.first
end
glob = File.join(Chef::Util::PathHelper.escape_glob_dir(spec.full_gem_path, dirs), glob)
- Dir[glob].map { |f| f.untaint }
+ Dir[glob].map(&:untaint)
end
def from_different_chef_version?(path)
diff --git a/lib/chef/knife/core/generic_presenter.rb b/lib/chef/knife/core/generic_presenter.rb
index f273cb5bca..850bfa8b3d 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@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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_relative "text_formatter"
class Chef
class Knife
@@ -25,20 +25,27 @@ class Chef
# Allows includer knife commands to return multiple attributes
# @brief knife node show NAME -a ATTR1 -a ATTR2
module MultiAttributeReturnOption
- # :nodoc:
+ # @private
def self.included(includer)
includer.class_eval do
- @attrs_to_show = []
+ option :field_separator,
+ short: "-S SEPARATOR",
+ long: "--field-separator SEPARATOR",
+ description: "Character separator used to delineate nesting in --attribute filters (default \".\")"
+
option :attribute,
- :short => "-a ATTR1 [-a ATTR2]",
- :long => "--attribute ATTR1 [--attribute ATTR2] ",
- :proc => lambda { |val| @attrs_to_show << val },
- :description => "Show one or more attributes"
+ short: "-a ATTR1 [-a ATTR2]",
+ long: "--attribute ATTR1 [--attribute ATTR2] ",
+ description: "Show one or more attributes",
+ proc: Proc.new { |arg, accumulator|
+ accumulator ||= []
+ accumulator << arg
+ accumulator
+ }
end
end
end
- #==Chef::Knife::Core::GenericPresenter
# The base presenter class for displaying structured data in knife commands.
# This is not an abstract base class, and it is suitable for displaying
# most kinds of objects that knife needs to display.
@@ -47,7 +54,7 @@ class Chef
attr_reader :ui
attr_reader :config
- # Instaniates a new GenericPresenter. This is generally handled by the
+ # Instantiates a new GenericPresenter. This is generally handled by the
# Chef::Knife::UI object, though you need to match the signature of this
# method if you intend to use your own presenter instead.
def initialize(ui, config)
@@ -80,10 +87,10 @@ class Chef
when :json
Chef::JSONCompat.to_json_pretty(data)
when :yaml
- require "yaml"
+ require "yaml" unless defined?(YAML)
YAML.dump(data)
when :pp
- require "stringio"
+ require "stringio" unless defined?(StringIO)
# If you were looking for some attribute and there is only one match
# just dump the attribute value
if config[:attribute] && data.length == 1
@@ -173,32 +180,35 @@ class Chef
config[:attribute] || config[:run_list]
end
+ # GenericPresenter is used in contexts where MultiAttributeReturnOption
+ # is not, so we need to set the default value here rather than as part
+ # of the CLI option.
+ def attribute_field_separator
+ config[:field_separator] || "."
+ end
+
def extract_nested_value(data, nested_value_spec)
- 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?(:[]) && 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
- end
+ nested_value_spec.split(attribute_field_separator).each do |attr|
+ data =
+ if data.is_a?(Array)
+ data[attr.to_i]
+ elsif data.respond_to?(:[], false) && data.respond_to?(:key?) && data.key?(attr)
+ data[attr]
+ elsif data.respond_to?(attr.to_sym, false)
+ # handles -a chef_environment and other things that hang of the node and aren't really attributes
+ data.public_send(attr.to_sym)
+ else
+ nil
+ end
end
- ( !data.kind_of?(Array) && data.respond_to?(:to_hash) ) ? data.to_hash : data
+ # necessary (?) for coercing objects (the run_list object?) to hashes
+ ( !data.is_a?(Array) && data.respond_to?(:to_hash) ) ? data.to_hash : data
end
def format_cookbook_list_for_display(item)
if config[:with_uri]
item.inject({}) do |collected, (cookbook, versions)|
- collected[cookbook] = Hash.new
+ collected[cookbook] = {}
versions["versions"].each do |ver|
collected[cookbook][ver["version"]] = ver["url"]
end
@@ -209,9 +219,9 @@ class Chef
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
+ key_length = versions_by_cookbook.empty? ? 0 : versions_by_cookbook.keys.map(&:size).max + 2
versions_by_cookbook.sort.map do |cookbook, versions|
- "#{cookbook.ljust(key_length)} #{versions.join(' ')}"
+ "#{cookbook.ljust(key_length)} #{versions.join(" ")}"
end
end
end
diff --git a/lib/chef/knife/core/hashed_command_loader.rb b/lib/chef/knife/core/hashed_command_loader.rb
index 8423c01812..c1d71f3edf 100644
--- a/lib/chef/knife/core/hashed_command_loader.rb
+++ b/lib/chef/knife/core/hashed_command_loader.rb
@@ -1,5 +1,5 @@
# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,7 +15,7 @@
# limitations under the License.
#
-require "chef/version"
+require_relative "../../version"
class Chef
class Knife
class SubcommandLoader
@@ -24,9 +24,10 @@ class Chef
# for the given command.
#
class HashedCommandLoader < Chef::Knife::SubcommandLoader
- KEY = "_autogenerated_command_paths"
+ KEY = "_autogenerated_command_paths".freeze
attr_accessor :manifest
+
def initialize(chef_config_dir, plugin_manifest)
super(chef_config_dir)
@manifest = plugin_manifest
@@ -44,7 +45,7 @@ class Chef
else
commands = manifest[KEY]["plugins_by_category"]
end
- # If any of the specified plugins in the manifest dont have a valid path we will
+ # If any of the specified plugins in the manifest don't have a valid path we will
# eventually get an error and the user will need to rehash - instead, lets just
# print out 1 error here telling them to rehash
errors = {}
@@ -52,7 +53,7 @@ class Chef
paths = manifest[KEY]["plugins_paths"][command]
if paths && paths.is_a?(Array)
# It is only an error if all the paths don't exist
- if paths.all? { |sc| !File.exists?(sc) }
+ if paths.all? { |sc| !File.exist?(sc) }
errors[command] = paths
end
end
@@ -60,7 +61,7 @@ class Chef
if errors.empty?
commands
else
- Chef::Log.error "There are files specified in the manifest that are missing. Please rehash to update the subcommands cache. If you see this error after rehashing delete the cache at #{Chef::Knife::SubcommandLoader.plugin_manifest_path}"
+ Chef::Log.error "There are plugin files specified in the knife cache that cannot be found. Please run knife rehash to update the subcommands cache. If you see this error after rehashing delete the cache at #{Chef::Knife::SubcommandLoader.plugin_manifest_path}"
Chef::Log.error "Missing files:\n\t#{errors.values.flatten.join("\n\t")}"
{}
end
@@ -76,7 +77,7 @@ class Chef
false
else
paths.each do |sc|
- if File.exists?(sc)
+ if File.exist?(sc)
Kernel.load sc
else
return false
diff --git a/lib/chef/knife/core/node_editor.rb b/lib/chef/knife/core/node_editor.rb
index b009bf8652..2f9b326d16 100644
--- a/lib/chef/knife/core/node_editor.rb
+++ b/lib/chef/knife/core/node_editor.rb
@@ -1,7 +1,7 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
# Author:: Jordan Running (<jr@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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/json_compat"
-require "chef/node"
+require_relative "../../json_compat"
+require_relative "../../node"
class Chef
class Knife
diff --git a/lib/chef/knife/core/node_presenter.rb b/lib/chef/knife/core/node_presenter.rb
index cdb664ec33..8c948cf76c 100644
--- a/lib/chef/knife/core/node_presenter.rb
+++ b/lib/chef/knife/core/node_presenter.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,39 +16,13 @@
# limitations under the License.
#
-require "chef/knife/core/text_formatter"
-require "chef/knife/core/generic_presenter"
+require_relative "text_formatter"
+require_relative "generic_presenter"
class Chef
class Knife
module Core
- # This module may be included into a knife subcommand class to automatically
- # add configuration options used by the NodePresenter
- module NodeFormattingOptions
- # :nodoc:
- # Would prefer to do this in a rational way, but can't be done b/c of
- # Mixlib::CLI's design :(
- def self.included(includer)
- includer.class_eval do
- option :medium_output,
- :short => "-m",
- :long => "--medium",
- :boolean => true,
- :default => false,
- :description => "Include normal attributes in the output"
-
- option :long_output,
- :short => "-l",
- :long => "--long",
- :boolean => true,
- :default => false,
- :description => "Include all attributes in the output"
- end
- end
- end
-
- #==Chef::Knife::Core::NodePresenter
# A customized presenter for Chef::Node objects. Supports variable-length
# output formats for displaying node data
class NodePresenter < GenericPresenter
@@ -62,7 +36,7 @@ class Chef
end
def summarize_json(data)
- if data.kind_of?(Chef::Node)
+ if data.is_a?(Chef::Node)
node = data
result = {}
@@ -93,55 +67,55 @@ class Chef
# the volume of output is adjusted accordingly. Uses colors if enabled
# in the ui object.
def summarize(data)
- if data.kind_of?(Chef::Node)
+ if data.is_a?(Chef::Node)
node = data
- # special case ec2 with their split horizon whatsis.
- ip = (node[:ec2] && node[:ec2][:public_ipv4]) || node[:ipaddress]
+ # special case clouds with their split horizon thing.
+ ip = (node[:cloud] && node[:cloud][:public_ipv4_addrs] && node[:cloud][:public_ipv4_addrs].first) || node[:ipaddress]
- summarized = <<-SUMMARY
-#{ui.color('Node Name:', :bold)} #{ui.color(node.name, :bold)}
-SUMMARY
+ 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
+ summarized << <<~POLICY
+ #{key("Policy Name:")} #{node.policy_name}
+ #{key("Policy Group:")} #{node.policy_group}
+ POLICY
else
- summarized << <<-ENV
-#{key('Environment:')} #{node.chef_environment}
-ENV
+ 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
+ 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
+ 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:')} #{node.tags.join(', ')}
-SUMMARY
+ summarized << <<~SUMMARY
+ #{key("Recipes:")} #{Array(node[:recipes]).join(", ")}
+ #{key("Platform:")} #{node[:platform]} #{node[:platform_version]}
+ #{key("Tags:")} #{node.tags.join(", ")}
+ SUMMARY
if config[:medium_output] || config[:long_output]
- summarized += <<-MORE
-#{key('Attributes:')}
-#{text_format(node.normal_attrs)}
-MORE
+ summarized += <<~MORE
+ #{key("Attributes:")}
+ #{text_format(node.normal_attrs)}
+ MORE
end
if config[:long_output]
- summarized += <<-MOST
-#{key('Default Attributes:')}
-#{text_format(node.default_attrs)}
-#{key('Override Attributes:')}
-#{text_format(node.override_attrs)}
-#{key('Automatic Attributes (Ohai Data):')}
-#{text_format(node.automatic_attrs)}
-MOST
+ summarized += <<~MOST
+ #{key("Default Attributes:")}
+ #{text_format(node.default_attrs)}
+ #{key("Override Attributes:")}
+ #{text_format(node.override_attrs)}
+ #{key("Automatic Attributes (Ohai Data):")}
+ #{text_format(node.automatic_attrs)}
+ MOST
end
summarized
else
diff --git a/lib/chef/knife/core/object_loader.rb b/lib/chef/knife/core/object_loader.rb
index b08483f9a2..5421fc46ce 100644
--- a/lib/chef/knife/core/object_loader.rb
+++ b/lib/chef/knife/core/object_loader.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "ffi_yajl"
-require "chef/util/path_helper"
-require "chef/data_bag_item"
+autoload :FFI_Yajl, "ffi_yajl"
+require_relative "../../util/path_helper"
+require_relative "../../data_bag_item"
class Chef
class Knife
@@ -40,7 +40,7 @@ class Chef
def load_from(repo_location, *components)
unless object_file = find_file(repo_location, *components)
- ui.error "Could not find or open file '#{components.last}' in current directory or in '#{repo_location}/#{components.join('/')}'"
+ ui.error "Could not find or open file '#{components.last}' in current directory or in '#{repo_location}/#{components.join("/")}'"
exit 1
end
object_from_file(object_file)
diff --git a/lib/chef/knife/core/status_presenter.rb b/lib/chef/knife/core/status_presenter.rb
index 68c1acf4f1..271c71d618 100644
--- a/lib/chef/knife/core/status_presenter.rb
+++ b/lib/chef/knife/core/status_presenter.rb
@@ -1,6 +1,6 @@
#
# Author:: Nicolas DUPEUX (<nicolas.dupeux@arkea.com>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,39 +16,13 @@
# limitations under the License.
#
-require "chef/knife/core/text_formatter"
-require "chef/knife/core/generic_presenter"
+require_relative "text_formatter"
+require_relative "generic_presenter"
class Chef
class Knife
module Core
- # This module may be included into a knife subcommand class to automatically
- # add configuration options used by the StatusPresenter
- module StatusFormattingOptions
- # :nodoc:
- # Would prefer to do this in a rational way, but can't be done b/c of
- # Mixlib::CLI's design :(
- def self.included(includer)
- includer.class_eval do
- option :medium_output,
- :short => "-m",
- :long => "--medium",
- :boolean => true,
- :default => false,
- :description => "Include normal attributes in the output"
-
- option :long_output,
- :short => "-l",
- :long => "--long",
- :boolean => true,
- :default => false,
- :description => "Include all attributes in the output"
- end
- end
- end
-
- #==Chef::Knife::Core::StatusPresenter
# A customized presenter for Chef::Node objects. Supports variable-length
# output formats for displaying node data
class StatusPresenter < GenericPresenter
@@ -68,8 +42,8 @@ class Chef
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"]
+ ip = (node["cloud"] && node["cloud"]["public_ipv4_addrs"]&.first) || node["ipaddress"]
+ fqdn = (node["cloud"] && node["cloud"]["public_hostname"]) || node["fqdn"]
result["ip"] = ip if ip
result["fqdn"] = fqdn if fqdn
result["run_list"] = node.run_list if config["run_list"]
@@ -96,36 +70,52 @@ class Chef
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]
+ # special case clouds with their split horizon thing.
+ ip = (node[:cloud] && node[:cloud][:public_ipv4_addrs] && node[:cloud][:public_ipv4_addrs].first) || node[:ipaddress]
+ fqdn = (node[:cloud] && node[:cloud][:public_hostname]) || node[:fqdn]
name = node["name"] || node.name
- 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]
- if hours > 24
- color = :red
- text = hours_text
- elsif hours >= 1
- color = :yellow
- text = hours_text
+ if config[:run_list]
+ if config[:long_output]
+ run_list = node.run_list.map { |rl| "#{rl.type}[#{rl.name}]" }
+ else
+ run_list = node["run_list"]
+ end
+ end
+
+ line_parts = []
+
+ if node["ohai_time"]
+ hours, minutes, seconds = time_difference_in_hms(node["ohai_time"])
+ hours_text = "#{hours} hour#{hours == 1 ? " " : "s"}"
+ minutes_text = "#{minutes} minute#{minutes == 1 ? " " : "s"}"
+ seconds_text = "#{seconds} second#{seconds == 1 ? " " : "s"}"
+ if hours > 24
+ color = :red
+ text = hours_text
+ elsif hours >= 1
+ color = :yellow
+ text = hours_text
+ elsif minutes >= 1
+ color = :green
+ text = minutes_text
+ else
+ color = :green
+ text = seconds_text
+ end
+ line_parts << @ui.color(text, color) + " ago" << name
else
- color = :green
- text = minutes_text
+ line_parts << "Node #{name} has not yet converged"
end
- line_parts = Array.new
- 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
+ line_parts << run_list.to_s if run_list
if node["platform"]
- platform = node["platform"]
+ platform = node["platform"].dup
if node["platform_version"]
- platform << " #{node['platform_version']}"
+ platform << " #{node["platform_version"]}"
end
line_parts << platform
end
@@ -139,8 +129,8 @@ class Chef
ui.color(key_text, :cyan)
end
- # :nodoc:
- # TODO: this is duplicated from StatusHelper in the Webui. dedup.
+ # @private
+ # @todo this is duplicated from StatusHelper in the Webui. dedup.
def time_difference_in_hms(unix_time)
now = Time.now.to_i
difference = now - unix_time.to_i
@@ -148,7 +138,7 @@ class Chef
difference = difference % 3600
minutes = (difference / 60).to_i
seconds = (difference % 60)
- return [hours, minutes, seconds]
+ [hours, minutes, seconds]
end
end
diff --git a/lib/chef/knife/core/subcommand_loader.rb b/lib/chef/knife/core/subcommand_loader.rb
index e617b39ded..26d7e0277c 100644
--- a/lib/chef/knife/core/subcommand_loader.rb
+++ b/lib/chef/knife/core/subcommand_loader.rb
@@ -1,6 +1,6 @@
# Author:: Christopher Brown (<cb@chef.io>)
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,11 +16,10 @@
# limitations under the License.
#
-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"
+require_relative "../../version"
+require_relative "../../util/path_helper"
+require_relative "gem_glob_loader"
+require_relative "hashed_command_loader"
class Chef
class Knife
@@ -33,12 +32,11 @@ class Chef
# 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
+ # command_class_from(args) - returns the subcommand class for the
# user-requested command
#
class SubcommandLoader
attr_reader :chef_config_dir
- attr_reader :env
# A small factory method. Eventually, this is the only place
# where SubcommandLoader should know about its subclasses, but
@@ -48,11 +46,8 @@ class Chef
# 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}")
+ Chef::Log.trace("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
@@ -72,10 +67,6 @@ class Chef
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
@@ -84,19 +75,33 @@ class Chef
Chef::Util::PathHelper.home(".chef", "plugin_manifest.json")
end
- def initialize(chef_config_dir, env = nil)
- @chef_config_dir = chef_config_dir
+ def self.generate_hash
+ output = if plugin_manifest?
+ 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
- # 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.")
+ def self.write_hash(data)
+ plugin_manifest_dir = File.expand_path("..", plugin_manifest_path)
+ FileUtils.mkdir_p(plugin_manifest_dir) unless File.directory?(plugin_manifest_dir)
+ File.open(plugin_manifest_path, "w") do |f|
+ f.write(Chef::JSONCompat.to_json_pretty(data))
end
end
+ def initialize(chef_config_dir)
+ @chef_config_dir = chef_config_dir
+ end
+
# Load all the sub-commands
def load_commands
return true if @loaded
+
subcommand_files.each { |subcommand| Kernel.load subcommand }
@loaded = true
end
@@ -123,7 +128,7 @@ class Chef
cmd_words = positional_arguments(args)
load_command(cmd_words)
result = Chef::Knife.subcommands[find_longest_key(Chef::Knife.subcommands,
- cmd_words, "_")]
+ cmd_words, "_")]
result || Chef::Knife.subcommands[args.first.tr("-", "_")]
end
@@ -131,7 +136,7 @@ class Chef
category_words = positional_arguments(args)
category_words.map! { |w| w.split("-") }.flatten!
find_longest_key(Chef::Knife.subcommands_by_category,
- category_words, " ")
+ category_words, " ")
end
#
@@ -139,7 +144,7 @@ class Chef
#
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_dir(File.expand_path("../../../knife", __FILE__)), "*.rb")]
+ files = Dir[File.join(Chef::Util::PathHelper.escape_glob_dir(File.expand_path("../../knife", __dir__)), "*.rb")]
subcommand_files = {}
files.each do |knife_file|
rel_path = knife_file[/#{CHEF_ROOT}#{Regexp.escape(File::SEPARATOR)}(.*)\.rb/, 1]
@@ -149,29 +154,15 @@ class Chef
end
#
- # 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
-
- #
# 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 = "_")
+ words = words.dup
match = nil
until match || words.empty?
- candidate = words.join(sep)
+ candidate = words.join(sep).tr("-", "_")
if hash.key?(candidate)
match = candidate
else
diff --git a/lib/chef/knife/core/text_formatter.rb b/lib/chef/knife/core/text_formatter.rb
index 8775e2e76f..ec97748afb 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@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -28,7 +28,7 @@ class Chef
@ui = ui
@data = if data.respond_to?(:display_hash)
data.display_hash
- elsif data.kind_of?(Array)
+ elsif data.is_a?(Array)
data
elsif data.respond_to?(:to_hash)
data.to_hash
@@ -48,7 +48,7 @@ class Chef
justify_width = data.keys.map { |k| k.to_s.size }.max.to_i + 1
data.sort.each do |key, value|
# key: ['value'] should be printed as key: value
- if value.kind_of?(Array) && value.size == 1 && is_singleton(value[0])
+ if value.is_a?(Array) && value.size == 1 && is_singleton(value[0])
value = value[0]
end
if is_singleton(value)
@@ -62,7 +62,7 @@ class Chef
lines.each { |line| buffer << " #{line}\n" }
end
end
- elsif data.kind_of?(Array)
+ elsif data.is_a?(Array)
data.each_index do |index|
item = data[index]
buffer << text_format(data[index])
@@ -77,7 +77,7 @@ class Chef
end
def is_singleton(value)
- !(value.kind_of?(Array) || value.respond_to?(:keys))
+ !(value.is_a?(Array) || value.respond_to?(:keys))
end
end
end
diff --git a/lib/chef/knife/core/ui.rb b/lib/chef/knife/core/ui.rb
index 67e431f1a7..aa84537064 100644
--- a/lib/chef/knife/core/ui.rb
+++ b/lib/chef/knife/core/ui.rb
@@ -2,7 +2,7 @@
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Brown (<cb@chef.io>)
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,15 +18,14 @@
# limitations under the License.
#
-require "forwardable"
-require "chef/platform/query_helpers"
-require "chef/knife/core/generic_presenter"
-require "tempfile"
+require "forwardable" unless defined?(Forwardable)
+require_relative "../../platform/query_helpers"
+require_relative "generic_presenter"
+require "tempfile" unless defined?(Tempfile)
class Chef
class Knife
- #==Chef::Knife::UI
# The User Interaction class used by knife.
class UI
@@ -62,48 +61,105 @@ class Chef
end
end
+ # Creates a new object of class TTY::Prompt
+ # with interrupt as exit so that it can be terminated with status code.
+ def prompt
+ @prompt ||= begin
+ require "tty-prompt"
+ TTY::Prompt.new(interrupt: :exit)
+ end
+ end
+
+ # pastel.decorate is a lightweight replacement for highline.color
+ def pastel
+ @pastel ||= begin
+ require "pastel" unless defined?(Pastel)
+ Pastel.new
+ end
+ end
+
# Prints a message to stdout. Aliased as +info+ for compatibility with
# the logger API.
+ #
+ # @param message [String] the text string
def msg(message)
- begin
- stdout.puts message
- rescue Errno::EPIPE => e
- raise e if @config[:verbosity] >= 2
- exit 0
- end
+ stdout.puts message
+ rescue Errno::EPIPE => e
+ raise e if @config[:verbosity] >= 2
+
+ exit 0
end
# Prints a msg to stderr. Used for info, warn, error, and fatal.
+ #
+ # @param message [String] the text string
def log(message)
- begin
- stderr.puts message
- rescue Errno::EPIPE => e
- raise e if @config[:verbosity] >= 2
- exit 0
+ lines = message.split("\n")
+ first_line = lines.shift
+ stderr.puts first_line
+ # If the message is multiple lines,
+ # indent subsequent lines to align with the
+ # log type prefix ("ERROR: ", etc)
+ unless lines.empty?
+ prefix, = first_line.split(":", 2)
+ return if prefix.nil?
+
+ prefix_len = prefix.length
+ prefix_len -= 9 if color? # prefix includes 9 bytes of color escape sequences
+ prefix_len += 2 # include room to align to the ": " following PREFIX
+ padding = " " * prefix_len
+ lines.each do |line|
+ stderr.puts "#{padding}#{line}"
+ end
end
+ rescue Errno::EPIPE => e
+ raise e if @config[:verbosity] >= 2
+
+ exit 0
end
alias :info :log
alias :err :log
+ # Print a Debug
+ #
+ # @param message [String] the text string
+ def debug(message)
+ log("#{color("DEBUG:", :blue, :bold)} #{message}")
+ end
+
# Print a warning message
+ #
+ # @param message [String] the text string
def warn(message)
- log("#{color('WARNING:', :yellow, :bold)} #{message}")
+ log("#{color("WARNING:", :yellow, :bold)} #{message}")
end
# Print an error message
+ #
+ # @param message [String] the text string
def error(message)
- log("#{color('ERROR:', :red, :bold)} #{message}")
+ log("#{color("ERROR:", :red, :bold)} #{message}")
end
# Print a message describing a fatal error.
+ #
+ # @param message [String] the text string
def fatal(message)
- log("#{color('FATAL:', :red, :bold)} #{message}")
+ log("#{color("FATAL:", :red, :bold)} #{message}")
+ end
+
+ # Print a message describing a fatal error and exit 1
+ #
+ # @param message [String] the text string
+ def fatal!(message)
+ fatal(message)
+ exit 1
end
def color(string, *colors)
if color?
- highline.color(string, *colors)
+ pastel.decorate(string, *colors)
else
string
end
@@ -116,8 +172,8 @@ class Chef
Chef::Config[:color] && stdout.tty?
end
- def ask(*args, &block)
- highline.ask(*args, &block)
+ def ask(*args, **options, &block)
+ prompt.ask(*args, **options, &block)
end
def list(*args)
@@ -138,7 +194,7 @@ class Chef
end
def ask_question(question, opts = {})
- question = question + "[#{opts[:default]}] " if opts[:default]
+ question += "[#{opts[:default]}] " if opts[:default]
if opts[:default] && config[:defaults]
opts[:default]
@@ -155,12 +211,11 @@ class Chef
end
def pretty_print(data)
- begin
- stdout.puts data
- rescue Errno::EPIPE => e
- raise e if @config[:verbosity] >= 2
- exit 0
- end
+ stdout.puts data
+ rescue Errno::EPIPE => e
+ raise e if @config[:verbosity] >= 2
+
+ exit 0
end
# Hash -> Hash
@@ -173,12 +228,12 @@ class Chef
def edit_data(data, parse_output = true, object_class: nil)
output = Chef::JSONCompat.to_json_pretty(data)
- if !config[:disable_editing]
+ unless config[:disable_editing]
Tempfile.open([ "knife-edit-", ".json" ]) do |tf|
tf.sync = true
tf.puts output
tf.close
- raise "Please set EDITOR environment variable" unless system("#{config[:editor]} #{tf.path}")
+ raise "Please set EDITOR environment variable. See https://docs.chef.io/knife_setup/ for details." unless system("#{config[:editor]} #{tf.path}")
output = IO.read(tf.path)
end
@@ -186,8 +241,7 @@ class Chef
if parse_output
if object_class.nil?
- Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please pass in the class to inflate or use #edit_hash")
- Chef::JSONCompat.from_json(output)
+ raise ArgumentError, "Please pass in the object class to hydrate or use #edit_hash"
else
object_class.from_hash(Chef::JSONCompat.parse(output))
end
@@ -215,9 +269,9 @@ class Chef
output_parsed_again = Chef::JSONCompat.parse(Chef::JSONCompat.to_json(output))
if object_parsed_again != output_parsed_again
output.save
- self.msg("Saved #{output}")
+ msg("Saved #{output}")
else
- self.msg("Object unchanged, not saving")
+ msg("Object unchanged, not saving")
end
output(format_for_display(object)) if config[:print_after]
end
@@ -247,19 +301,19 @@ class Chef
when "Y", "y"
true
when "N", "n"
- self.msg("You said no, so I'm done here.")
+ msg("You said no, so I'm done here.")
false
when ""
unless default_choice.nil?
default_choice
else
- self.msg("I have no idea what to do with '#{answer}'")
- self.msg("Just say Y or N, please.")
+ msg("I have no idea what to do with '#{answer}'")
+ msg("Just say Y or N, please.")
confirm_without_exit(question, append_instructions, default_choice)
end
else
- self.msg("I have no idea what to do with '#{answer}'")
- self.msg("Just say Y or N, please.")
+ msg("I have no idea what to do with '#{answer}'")
+ msg("Just say Y or N, please.")
confirm_without_exit(question, append_instructions, default_choice)
end
end
diff --git a/lib/chef/knife/core/windows_bootstrap_context.rb b/lib/chef/knife/core/windows_bootstrap_context.rb
new file mode 100644
index 0000000000..fa8b43f383
--- /dev/null
+++ b/lib/chef/knife/core/windows_bootstrap_context.rb
@@ -0,0 +1,406 @@
+#
+# Author:: Seth Chisamore (<schisamo@chef.io>)
+# Copyright:: Copyright (c) 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_relative "bootstrap_context"
+require_relative "../../util/path_helper"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Knife
+ module Core
+ # Instances of BootstrapContext are the context objects (i.e., +self+) for
+ # bootstrap templates. For backwards compatibility, they +must+ set the
+ # following instance variables:
+ # * @config - a hash of knife's config values
+ # * @run_list - the run list for the node to bootstrap
+ #
+ class WindowsBootstrapContext < BootstrapContext
+ attr_accessor :config
+ attr_accessor :chef_config
+ attr_accessor :secret
+
+ def initialize(config, run_list, chef_config, secret = nil)
+ @config = config
+ @run_list = run_list
+ @chef_config = chef_config
+ @secret = secret
+ super(config, run_list, chef_config, secret)
+ end
+
+ def validation_key
+ if File.exist?(File.expand_path(chef_config[:validation_key]))
+ IO.read(File.expand_path(chef_config[:validation_key]))
+ else
+ false
+ end
+ end
+
+ def encrypted_data_bag_secret
+ escape_and_echo(@secret)
+ end
+
+ def trusted_certs_script
+ @trusted_certs_script ||= trusted_certs_content
+ end
+
+ def config_content
+ # The windows: true / windows: false in the block that follows is more than a bit weird. The way to read this is that we need
+ # the e.g. var_chef_dir to be rendered for the windows value ("C:\chef"), but then we are rendering into a file to be read by
+ # ruby, so we don't actually care about forward-vs-backslashes and by rendering into unix we avoid having to deal with the
+ # double-backwhacking of everything. So we expect to see:
+ #
+ # file_cache_path "C:/chef"
+ #
+ # Which is mildly odd, but should be entirely correct as far as ruby cares.
+ #
+ client_rb = <<~CONFIG
+ chef_server_url "#{chef_config[:chef_server_url]}"
+ validation_client_name "#{chef_config[:validation_client_name]}"
+ file_cache_path "#{ChefConfig::PathHelper.escapepath(ChefConfig::Config.var_chef_dir(windows: true))}\\\\cache"
+ file_backup_path "#{ChefConfig::PathHelper.escapepath(ChefConfig::Config.var_chef_dir(windows: true))}\\\\backup"
+ cache_options ({:path => "#{ChefConfig::PathHelper.escapepath(ChefConfig::Config.etc_chef_dir(windows: true))}\\\\cache\\\\checksums", :skip_expires => true})
+ CONFIG
+
+ unless chef_config[:chef_license].nil?
+ client_rb << "chef_license \"#{chef_config[:chef_license]}\"\n"
+ end
+
+ if config[:chef_node_name]
+ client_rb << %Q{node_name "#{config[:chef_node_name]}"\n}
+ else
+ client_rb << "# Using default node name (fqdn)\n"
+ end
+
+ if config[:config_log_level]
+ client_rb << %Q{log_level :#{config[:config_log_level]}\n}
+ else
+ client_rb << "log_level :auto\n"
+ end
+
+ client_rb << "log_location #{get_log_location}"
+
+ # We configure :verify_api_cert only when it's overridden on the CLI
+ # or when specified in the knife config.
+ if !config[:node_verify_api_cert].nil? || config.key?(:verify_api_cert)
+ value = config[:node_verify_api_cert].nil? ? config[:verify_api_cert] : config[:node_verify_api_cert]
+ client_rb << %Q{verify_api_cert #{value}\n}
+ end
+
+ # We configure :ssl_verify_mode only when it's overridden on the CLI
+ # or when specified in the knife config.
+ if config[:node_ssl_verify_mode] || config.key?(:ssl_verify_mode)
+ value = case config[:node_ssl_verify_mode]
+ when "peer"
+ :verify_peer
+ when "none"
+ :verify_none
+ when nil
+ config[:ssl_verify_mode]
+ else
+ nil
+ end
+
+ if value
+ client_rb << %Q{ssl_verify_mode :#{value}\n}
+ end
+ end
+
+ if config[:ssl_verify_mode]
+ client_rb << %Q{ssl_verify_mode :#{config[:ssl_verify_mode]}\n}
+ end
+
+ if config[:bootstrap_proxy]
+ client_rb << "\n"
+ client_rb << %Q{http_proxy "#{config[:bootstrap_proxy]}"\n}
+ client_rb << %Q{https_proxy "#{config[:bootstrap_proxy]}"\n}
+ client_rb << %Q{no_proxy "#{config[:bootstrap_no_proxy]}"\n} if config[:bootstrap_no_proxy]
+ end
+
+ if config[:bootstrap_no_proxy]
+ client_rb << %Q{no_proxy "#{config[:bootstrap_no_proxy]}"\n}
+ end
+
+ if secret
+ client_rb << %Q{encrypted_data_bag_secret "#{ChefConfig::PathHelper.escapepath(ChefConfig::Config.etc_chef_dir(windows: true))}\\\\encrypted_data_bag_secret"\n}
+ end
+
+ unless trusted_certs_script.empty?
+ client_rb << %Q{trusted_certs_dir "#{ChefConfig::PathHelper.escapepath(ChefConfig::Config.etc_chef_dir(windows: true))}\\\\trusted_certs"\n}
+ end
+
+ if chef_config[:fips]
+ client_rb << "fips true\n"
+ end
+
+ escape_and_echo(client_rb)
+ end
+
+ def get_log_location
+ if chef_config[:config_log_location].equal?(:win_evt)
+ %Q{:#{chef_config[:config_log_location]}\n}
+ elsif chef_config[:config_log_location].equal?(:syslog)
+ raise "syslog is not supported for log_location on Windows OS\n"
+ elsif chef_config[:config_log_location].equal?(STDOUT)
+ "STDOUT\n"
+ elsif chef_config[:config_log_location].equal?(STDERR)
+ "STDERR\n"
+ elsif chef_config[:config_log_location].nil? || chef_config[:config_log_location].empty?
+ "STDOUT\n"
+ elsif chef_config[:config_log_location]
+ %Q{"#{chef_config[:config_log_location]}"\n}
+ else
+ "STDOUT\n"
+ end
+ end
+
+ def start_chef
+ c_opscode_dir = ChefConfig::PathHelper.cleanpath(ChefConfig::Config.c_opscode_dir, windows: true)
+ client_rb = clean_etc_chef_file("client.rb")
+ first_boot = clean_etc_chef_file("first-boot.json")
+
+ bootstrap_environment_option = bootstrap_environment.nil? ? "" : " -E #{bootstrap_environment}"
+
+ start_chef = "SET \"PATH=%SYSTEM32%;%SystemRoot%;%SYSTEM32%\\Wbem;%SYSTEM32%\\WindowsPowerShell\\v1.0\\;C:\\ruby\\bin;#{c_opscode_dir}\\bin;#{c_opscode_dir}\\embedded\\bin\;%PATH%\"\n"
+ start_chef << "#{ChefUtils::Dist::Infra::CLIENT} -c #{client_rb} -j #{first_boot}#{bootstrap_environment_option}\n"
+ end
+
+ def win_wget
+ # I tried my best to figure out how to properly url decode and switch / to \
+ # but this is VBScript - so I don't really care that badly.
+ win_wget = <<~WGET
+ url = WScript.Arguments.Named("url")
+ path = WScript.Arguments.Named("path")
+ proxy = null
+ '* Vaguely attempt to handle file:// scheme urls by url unescaping and switching all
+ '* / into \. Also assume that file:/// is a local absolute path and that file://<foo>
+ '* is possibly a network file path.
+ If InStr(url, "file://") = 1 Then
+ url = Unescape(url)
+ If InStr(url, "file:///") = 1 Then
+ sourcePath = Mid(url, Len("file:///") + 1)
+ Else
+ sourcePath = Mid(url, Len("file:") + 1)
+ End If
+ sourcePath = Replace(sourcePath, "/", "\\")
+
+ Set objFSO = CreateObject("Scripting.FileSystemObject")
+ If objFSO.Fileexists(path) Then objFSO.DeleteFile path
+ objFSO.CopyFile sourcePath, path, true
+ Set objFSO = Nothing
+
+ Else
+ Set objXMLHTTP = CreateObject("MSXML2.ServerXMLHTTP")
+ Set wshShell = CreateObject( "WScript.Shell" )
+ Set objUserVariables = wshShell.Environment("USER")
+
+ rem http proxy is optional
+ rem attempt to read from HTTP_PROXY env var first
+ On Error Resume Next
+
+ If NOT (objUserVariables("HTTP_PROXY") = "") Then
+ proxy = objUserVariables("HTTP_PROXY")
+
+ rem fall back to named arg
+ ElseIf NOT (WScript.Arguments.Named("proxy") = "") Then
+ proxy = WScript.Arguments.Named("proxy")
+ End If
+
+ If NOT isNull(proxy) Then
+ rem setProxy method is only available on ServerXMLHTTP 6.0+
+ Set objXMLHTTP = CreateObject("MSXML2.ServerXMLHTTP.6.0")
+ objXMLHTTP.setProxy 2, proxy
+ End If
+
+ On Error Goto 0
+
+ objXMLHTTP.open "GET", url, false
+ objXMLHTTP.send()
+ If objXMLHTTP.Status = 200 Then
+ Set objADOStream = CreateObject("ADODB.Stream")
+ objADOStream.Open
+ objADOStream.Type = 1
+ objADOStream.Write objXMLHTTP.ResponseBody
+ objADOStream.Position = 0
+ Set objFSO = Createobject("Scripting.FileSystemObject")
+ If objFSO.Fileexists(path) Then objFSO.DeleteFile path
+ Set objFSO = Nothing
+ objADOStream.SaveToFile path
+ objADOStream.Close
+ Set objADOStream = Nothing
+ End If
+ Set objXMLHTTP = Nothing
+ End If
+ WGET
+ escape_and_echo(win_wget)
+ end
+
+ def win_wget_ps
+ win_wget_ps = <<~WGET_PS
+ param(
+ [String] $remoteUrl,
+ [String] $localPath
+ )
+
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+
+ $ProxyUrl = $env:http_proxy;
+ $webClient = new-object System.Net.WebClient;
+
+ if ($ProxyUrl -ne '') {
+ $WebProxy = New-Object System.Net.WebProxy($ProxyUrl,$true)
+ $WebClient.Proxy = $WebProxy
+ }
+
+ $webClient.DownloadFile($remoteUrl, $localPath);
+ WGET_PS
+
+ escape_and_echo(win_wget_ps)
+ end
+
+ def install_chef
+ # The normal install command uses regular double quotes in
+ # the install command, so request such a string from install_command
+ install_command('"') + "\n" + fallback_install_task_command
+ end
+
+ def clean_etc_chef_file(path)
+ ChefConfig::PathHelper.cleanpath(etc_chef_file(path), windows: true)
+ end
+
+ def etc_chef_file(path)
+ "#{bootstrap_directory}/#{path}"
+ end
+
+ def bootstrap_directory
+ ChefConfig::Config.etc_chef_dir(windows: true)
+ end
+
+ def local_download_path
+ "%TEMP%\\#{ChefUtils::Dist::Infra::CLIENT}-latest.msi"
+ end
+
+ # Build a URL to query www.chef.io that will redirect to the correct
+ # Chef Infra msi download.
+ def msi_url(machine_os = nil, machine_arch = nil, download_context = nil)
+ if config[:msi_url].nil? || config[:msi_url].empty?
+ url = "https://www.chef.io/chef/download?p=windows"
+ url += "&pv=#{machine_os}" unless machine_os.nil?
+ url += "&m=#{machine_arch}" unless machine_arch.nil?
+ url += "&DownloadContext=#{download_context}" unless download_context.nil?
+ url += "&channel=#{config[:channel]}"
+ url += "&v=#{version_to_install}"
+ else
+ config[:msi_url]
+ end
+ end
+
+ def first_boot
+ escape_and_echo(super.to_json)
+ end
+
+ # escape WIN BATCH special chars
+ # and prefixes each line with an
+ # echo
+ def escape_and_echo(file_contents)
+ file_contents.gsub(/^(.*)$/, 'echo.\1').gsub(/([(<|>)^])/, '^\1')
+ end
+
+ private
+
+ def install_command(executor_quote)
+ "msiexec /qn /log #{executor_quote}%CHEF_CLIENT_MSI_LOG_PATH%#{executor_quote} /i #{executor_quote}%LOCAL_DESTINATION_MSI_PATH%#{executor_quote}"
+ end
+
+ # 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
+ content = ""
+ if chef_config[:trusted_certs_dir]
+ Dir.glob(File.join(Chef::Util::PathHelper.escape_glob_dir(chef_config[:trusted_certs_dir]), "*.{crt,pem}")).each do |cert|
+ content << "> #{bootstrap_directory}/trusted_certs/#{File.basename(cert)} (\n" +
+ escape_and_echo(IO.read(File.expand_path(cert))) + "\n)\n"
+ end
+ end
+ content
+ end
+
+ def client_d_content
+ content = ""
+ if chef_config[:client_d_dir] && File.exist?(chef_config[:client_d_dir])
+ root = Pathname(chef_config[:client_d_dir])
+ root.find do |f|
+ relative = f.relative_path_from(root)
+ if f != root
+ file_on_node = "#{bootstrap_directory}/client.d/#{relative}".tr("/", "\\")
+ if f.directory?
+ content << "mkdir #{file_on_node}\n"
+ else
+ content << "> #{file_on_node} (\n" +
+ escape_and_echo(IO.read(File.expand_path(f))) + "\n)\n"
+ end
+ end
+ end
+ end
+ content
+ end
+
+ def fallback_install_task_command
+ # This command will be executed by schtasks.exe in the batch
+ # code below. To handle tasks that contain arguments that
+ # need to be double quoted, schtasks allows the use of single
+ # quotes that will later be converted to double quotes
+ command = install_command("'")
+ <<~EOH
+ @set MSIERRORCODE=!ERRORLEVEL!
+ @if ERRORLEVEL 1 (
+ @echo WARNING: Failed to install #{ChefUtils::Dist::Infra::PRODUCT} MSI package in remote context with status code !MSIERRORCODE!.
+ @echo WARNING: This may be due to a defect in operating system update KB2918614: http://support.microsoft.com/kb/2918614
+ @set OLDLOGLOCATION="%CHEF_CLIENT_MSI_LOG_PATH%-fail.log"
+ @move "%CHEF_CLIENT_MSI_LOG_PATH%" "!OLDLOGLOCATION!" > NUL
+ @echo WARNING: Saving installation log of failure at !OLDLOGLOCATION!
+ @echo WARNING: Retrying installation with local context...
+ @schtasks /create /f /sc once /st 00:00:00 /tn chefclientbootstraptask /ru SYSTEM /rl HIGHEST /tr \"cmd /c #{command} & sleep 2 & waitfor /s %computername% /si chefclientinstalldone\"
+
+ @if ERRORLEVEL 1 (
+ @echo ERROR: Failed to create #{ChefUtils::Dist::Infra::PRODUCT} installation scheduled task with status code !ERRORLEVEL! > "&2"
+ ) else (
+ @echo Successfully created scheduled task to install #{ChefUtils::Dist::Infra::PRODUCT}.
+ @schtasks /run /tn chefclientbootstraptask
+ @if ERRORLEVEL 1 (
+ @echo ERROR: Failed to execute #{ChefUtils::Dist::Infra::PRODUCT} installation scheduled task with status code !ERRORLEVEL!. > "&2"
+ ) else (
+ @echo Successfully started #{ChefUtils::Dist::Infra::PRODUCT} installation scheduled task.
+ @echo Waiting for installation to complete -- this may take a few minutes...
+ waitfor chefclientinstalldone /t 600
+ if ERRORLEVEL 1 (
+ @echo ERROR: Timed out waiting for #{ChefUtils::Dist::Infra::PRODUCT} package to install
+ ) else (
+ @echo Finished waiting for #{ChefUtils::Dist::Infra::PRODUCT} package to install.
+ )
+ @schtasks /delete /f /tn chefclientbootstraptask > NUL
+ )
+ )
+ ) else (
+ @echo Successfully installed #{ChefUtils::Dist::Infra::PRODUCT} package.
+ )
+ EOH
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/data_bag_create.rb b/lib/chef/knife/data_bag_create.rb
index 196278bb80..11448c69b7 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@chef.io>)
# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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_relative "../knife"
+require_relative "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_relative "../data_bag"
+ require_relative "../encrypted_data_bag_item"
end
banner "knife data bag create BAG [ITEM] (options)"
@@ -49,13 +49,16 @@ class Chef
exit(1)
end
- # create the data bag
+ # Verify if the data bag exists
begin
+ rest.get("data/#{@data_bag_name}")
+ ui.info("Data bag #{@data_bag_name} already exists")
+ rescue Net::HTTPClientException => e
+ raise unless /^404/.match?(e.to_s)
+
+ # if it doesn't exists, try to create it
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/
- ui.info("Data bag #{@data_bag_name} already exists")
end
# if an item is specified, create it, as well
diff --git a/lib/chef/knife/data_bag_delete.rb b/lib/chef/knife/data_bag_delete.rb
index c1ea2013c8..ab38244e91 100644
--- a/lib/chef/knife/data_bag_delete.rb
+++ b/lib/chef/knife/data_bag_delete.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,14 +16,14 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class DataBagDelete < Knife
deps do
- require "chef/data_bag"
+ require_relative "../data_bag"
end
banner "knife data bag delete BAG [ITEM] (options)"
diff --git a/lib/chef/knife/data_bag_edit.rb b/lib/chef/knife/data_bag_edit.rb
index 5d76762058..1935f2149e 100644
--- a/lib/chef/knife/data_bag_edit.rb
+++ b/lib/chef/knife/data_bag_edit.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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_relative "../knife"
+require_relative "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_relative "../data_bag_item"
+ require_relative "../encrypted_data_bag_item"
end
banner "knife data bag edit BAG ITEM (options)"
@@ -37,13 +37,13 @@ class Chef
item = Chef::DataBagItem.load(bag, item_name)
if encrypted?(item.raw_data)
if encryption_secret_provided_ignore_encrypt_flag?
- return Chef::EncryptedDataBagItem.new(item, read_secret).to_hash, true
+ [Chef::EncryptedDataBagItem.new(item, read_secret).to_hash, true]
else
ui.fatal("You cannot edit an encrypted data bag without providing the secret.")
exit(1)
end
else
- return item, false
+ [item.raw_data, false]
end
end
diff --git a/lib/chef/knife/data_bag_from_file.rb b/lib/chef/knife/data_bag_from_file.rb
index 30b9de3386..5f48b0a934 100644
--- a/lib/chef/knife/data_bag_from_file.rb
+++ b/lib/chef/knife/data_bag_from_file.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,9 +17,8 @@
# limitations under the License.
#
-require "chef/knife"
-require "chef/util/path_helper"
-require "chef/knife/data_bag_secret_options"
+require_relative "../knife"
+require_relative "data_bag_secret_options"
class Chef
class Knife
@@ -27,20 +26,20 @@ 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_relative "../util/path_helper"
+ require_relative "../data_bag"
+ require_relative "../data_bag_item"
+ require_relative "core/object_loader"
+ require_relative "../encrypted_data_bag_item"
end
banner "knife data bag from file BAG FILE|FOLDER [FILE|FOLDER..] (options)"
category "data bag"
option :all,
- :short => "-a",
- :long => "--all",
- :description => "Upload all data bags or all items for specified data bags"
+ short: "-a",
+ long: "--all",
+ description: "Upload all data bags or all items for specified data bags."
def loader
@loader ||= Knife::Core::ObjectLoader.new(DataBagItem, ui)
@@ -84,7 +83,7 @@ class Chef
items ||= find_all_data_bag_items(data_bag)
item_paths = normalize_item_paths(items)
item_paths.each do |item_path|
- item = loader.load_from("#{data_bags_path}", data_bag, item_path)
+ item = loader.load_from((data_bags_path).to_s, data_bag, item_path)
item = if encryption_secret_provided?
Chef::EncryptedDataBagItem.encrypt_data_bag_item(item, read_secret)
else
@@ -99,7 +98,7 @@ class Chef
end
def normalize_item_paths(args)
- paths = Array.new
+ paths = []
args.each do |path|
if File.directory?(path)
paths.concat(Dir.glob(File.join(Chef::Util::PathHelper.escape_glob_dir(path), "*.json")))
diff --git a/lib/chef/knife/data_bag_list.rb b/lib/chef/knife/data_bag_list.rb
index d507925ec8..801bf588b4 100644
--- a/lib/chef/knife/data_bag_list.rb
+++ b/lib/chef/knife/data_bag_list.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,23 +16,23 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class DataBagList < Knife
deps do
- require "chef/data_bag"
+ require_relative "../data_bag"
end
banner "knife data bag list (options)"
category "data bag"
option :with_uri,
- :short => "-w",
- :long => "--with-uri",
- :description => "Show corresponding URIs"
+ short: "-w",
+ long: "--with-uri",
+ description: "Show corresponding URIs."
def run
output(format_list_for_display(Chef::DataBag.list))
diff --git a/lib/chef/knife/data_bag_secret_options.rb b/lib/chef/knife/data_bag_secret_options.rb
index b426cd442c..8f9f96502f 100644
--- a/lib/chef/knife/data_bag_secret_options.rb
+++ b/lib/chef/knife/data_bag_secret_options.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Ball (<tball@chef.io>)
-# Copyright:: Copyright 2014-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -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" unless defined?(Mixlib::CLI)
+require_relative "../config"
+require_relative "../encrypted_data_bag_item/check_encrypted"
class Chef
class Knife
@@ -34,24 +34,19 @@ class Chef
# are provided.
def self.included(base)
- base.option :secret,
- :short => "-s SECRET",
- :long => "--secret ",
- :description => "The secret key to use to encrypt data bag item values. Can also be defaulted in your config with the key 'secret'",
- # Need to store value from command line in separate variable - knife#merge_configs populates same keys
- # on config object from
- :proc => Proc.new { |s| set_cl_secret(s) }
-
- base.option :secret_file,
- :long => "--secret-file SECRET_FILE",
- :description => "A file containing the secret key to use to encrypt data bag item values. Can also be defaulted in your config with the key 'secret_file'",
- :proc => Proc.new { |sf| set_cl_secret_file(sf) }
+ base.option :cl_secret,
+ long: "--secret SECRET",
+ description: "The secret key to use to encrypt data bag item values. Can also be defaulted in your config with the key 'secret'."
+
+ base.option :cl_secret_file,
+ long: "--secret-file SECRET_FILE",
+ description: "A file containing the secret key to use to encrypt data bag item values. Can also be defaulted in your config with the key 'secret_file'."
base.option :encrypt,
- :long => "--encrypt",
- :description => "If 'secret' or 'secret_file' is present in your config, then encrypt data bags using it",
- :boolean => true,
- :default => false
+ long: "--encrypt",
+ description: "If 'secret' or 'secret_file' is present in your config, then encrypt data bags using it.",
+ boolean: true,
+ default: false
end
def encryption_secret_provided?
@@ -65,27 +60,27 @@ 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_relative "../encrypted_data_bag_item"
- if has_cl_secret?
- config[:secret]
- elsif has_cl_secret_file?
- Chef::EncryptedDataBagItem.load_secret(config[:secret_file])
- elsif secret = knife_config[:secret]
+ if config[:cl_secret]
+ config[:cl_secret]
+ elsif config[:cl_secret_file]
+ Chef::EncryptedDataBagItem.load_secret(config[:cl_secret_file])
+ elsif secret = config[:secret]
secret
else
- secret_file = knife_config[:secret_file]
+ secret_file = config[:secret_file]
Chef::EncryptedDataBagItem.load_secret(secret_file)
end
end
def validate_secrets
- if has_cl_secret? && has_cl_secret_file?
+ if config[:cl_secret] && config[:cl_secret_file]
ui.fatal("Please specify only one of --secret, --secret-file")
exit(1)
end
- if knife_config[:secret] && knife_config[:secret_file]
+ if config[:secret] && config[:secret_file]
ui.fatal("Please specify only one of 'secret' or 'secret_file' in your config file")
exit(1)
end
@@ -95,45 +90,30 @@ class Chef
##
# Determine if the user has specified an appropriate secret for encrypting data bag items.
- # @returns boolean
+ # @return boolean
def base_encryption_secret_provided?(need_encrypt_flag = true)
validate_secrets
- return true if has_cl_secret? || has_cl_secret_file?
+ return true if config[:cl_secret] || config[:cl_secret_file]
if need_encrypt_flag
if config[:encrypt]
- unless knife_config[:secret] || knife_config[:secret_file]
+ unless config[:secret] || config[:secret_file]
ui.fatal("No secret or secret_file specified in config, unable to encrypt item.")
exit(1)
end
return true
end
return false
- elsif knife_config[:secret] || knife_config[:secret_file]
+ elsif config[:secret] || config[:secret_file]
# Certain situations (show and bootstrap) don't need a --encrypt flag to use the config file secret
return true
end
- return false
- end
-
- def has_cl_secret?
- Chef::Config[:knife].has_key?(:cl_secret)
- end
-
- def self.set_cl_secret(s)
- Chef::Config[:knife][:cl_secret] = s
- end
-
- def has_cl_secret_file?
- Chef::Config[:knife].has_key?(:cl_secret_file)
- end
-
- def self.set_cl_secret_file(sf)
- Chef::Config[:knife][:cl_secret_file] = sf
+ false
end
def knife_config
+ Chef.deprecated(:knife_bootstrap_apis, "The `knife_config` bootstrap helper has been deprecated, use the properly merged `config` helper instead")
Chef::Config.key?(:knife) ? Chef::Config[:knife] : {}
end
diff --git a/lib/chef/knife/data_bag_show.rb b/lib/chef/knife/data_bag_show.rb
index ea9c390f19..cb7b56c333 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@chef.io>)
# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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_relative "../knife"
+require_relative "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_relative "../data_bag"
+ require_relative "../encrypted_data_bag_item"
end
banner "knife data bag show BAG [ITEM] (options)"
@@ -44,14 +44,14 @@ class Chef
# 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)
+ @name_args[1],
+ secret)
+ format_for_display(raw.to_h)
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.warn("Unencrypted data bag detected, ignoring any provided secret options.")
+ ui.warn("Unencrypted data bag detected, ignoring any provided secret options.") if secret
format_for_display(raw_data)
end
diff --git a/lib/chef/knife/delete.rb b/lib/chef/knife/delete.rb
index cf6ca09878..3e5c545017 100644
--- a/lib/chef/knife/delete.rb
+++ b/lib/chef/knife/delete.rb
@@ -1,4 +1,20 @@
-require "chef/chef_fs/knife"
+#
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../chef_fs/knife"
class Chef
class Knife
@@ -8,25 +24,27 @@ class Chef
category "path-based"
deps do
- require "chef/chef_fs/file_system"
+ require_relative "../chef_fs/file_system"
end
option :recurse,
- :short => "-r",
- :long => "--[no-]recurse",
- :boolean => true,
- :default => false,
- :description => "Delete directories recursively."
+ short: "-r",
+ long: "--[no-]recurse",
+ boolean: true,
+ default: false,
+ description: "Delete directories recursively."
+
option :both,
- :long => "--both",
- :boolean => true,
- :default => false,
- :description => "Delete both the local and remote copies."
+ long: "--both",
+ boolean: true,
+ default: false,
+ description: "Delete both the local and remote copies."
+
option :local,
- :long => "--local",
- :boolean => true,
- :default => false,
- :description => "Delete the local copy (leave the remote copy)."
+ long: "--local",
+ boolean: true,
+ default: false,
+ description: "Delete the local copy (leave the remote copy)."
def run
if name_args.length == 0
@@ -78,21 +96,21 @@ class Chef
found_any = false
error = false
results.each do |result|
- begin
- result.delete(config[:recurse])
- deleted_any = true
- found_any = true
- rescue Chef::ChefFS::FileSystem::NotFoundError
- # This is not an error unless *all* of them were not found
- rescue Chef::ChefFS::FileSystem::MustDeleteRecursivelyError => e
- ui.error "#{format_path_with_root(e.entry)} must be deleted recursively! Pass -r to knife delete."
- found_any = true
- error = true
- rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e
- ui.error "#{format_path_with_root(e.entry)} #{e.reason}."
- found_any = true
- error = true
- end
+
+ result.delete(config[:recurse])
+ deleted_any = true
+ found_any = true
+ rescue Chef::ChefFS::FileSystem::NotFoundError
+ # This is not an error unless *all* of them were not found
+ rescue Chef::ChefFS::FileSystem::MustDeleteRecursivelyError => e
+ ui.error "#{format_path_with_root(e.entry)} must be deleted recursively! Pass -r to knife delete."
+ found_any = true
+ error = true
+ rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e
+ ui.error "#{format_path_with_root(e.entry)} #{e.reason}."
+ found_any = true
+ error = true
+
end
if deleted_any
output("Deleted #{format_path(results[0])}")
diff --git a/lib/chef/knife/deps.rb b/lib/chef/knife/deps.rb
index e773f65106..f620b53bfa 100644
--- a/lib/chef/knife/deps.rb
+++ b/lib/chef/knife/deps.rb
@@ -1,4 +1,20 @@
-require "chef/chef_fs/knife"
+#
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../chef_fs/knife"
class Chef
class Knife
@@ -8,22 +24,24 @@ class Chef
category "path-based"
deps do
- require "chef/chef_fs/file_system"
- require "chef/run_list"
+ require_relative "../chef_fs/file_system"
+ require_relative "../run_list"
end
option :recurse,
- :long => "--[no-]recurse",
- :boolean => true,
- :description => "List dependencies recursively (default: true). Only works with --tree."
+ long: "--[no-]recurse",
+ boolean: true,
+ description: "List dependencies recursively (default: true). Only works with --tree."
+
option :tree,
- :long => "--tree",
- :boolean => true,
- :description => "Show dependencies in a visual tree. May show duplicates."
+ long: "--tree",
+ boolean: true,
+ description: "Show dependencies in a visual tree. May show duplicates."
+
option :remote,
- :long => "--remote",
- :boolean => true,
- :description => "List dependencies on the server instead of the local filesystem"
+ long: "--remote",
+ boolean: true,
+ description: "List dependencies on the server instead of the local filesystem."
attr_accessor :exit_code
@@ -49,7 +67,7 @@ class Chef
end
def print_flattened_dependencies(entry, dependencies)
- if !dependencies[entry.path]
+ unless dependencies[entry.path]
dependencies[entry.path] = get_dependencies(entry)
dependencies[entry.path].each do |child|
child_entry = Chef::ChefFS::FileSystem.resolve_path(@root, child)
@@ -60,8 +78,8 @@ class Chef
end
def print_dependencies_tree(entry, dependencies, printed = {}, depth = 0)
- dependencies[entry.path] = get_dependencies(entry) if !dependencies[entry.path]
- output "#{' ' * depth}#{format_path(entry)}"
+ dependencies[entry.path] = get_dependencies(entry) unless dependencies[entry.path]
+ output "#{" " * depth}#{format_path(entry)}"
if !printed[entry.path] && (config[:recurse] || depth == 0)
printed[entry.path] = true
dependencies[entry.path].each do |child|
@@ -72,49 +90,47 @@ class Chef
end
def get_dependencies(entry)
- begin
- if entry.parent && entry.parent.path == "/cookbooks"
- return entry.chef_object.metadata.dependencies.keys.map { |cookbook| "/cookbooks/#{cookbook}" }
-
- elsif entry.parent && entry.parent.path == "/nodes"
- node = Chef::JSONCompat.parse(entry.read)
- result = []
- if node["chef_environment"] && node["chef_environment"] != "_default"
- result << "/environments/#{node['chef_environment']}.json"
- end
- if node["run_list"]
- result += dependencies_from_runlist(node["run_list"])
- end
- result
-
- elsif entry.parent && entry.parent.path == "/roles"
- role = Chef::JSONCompat.parse(entry.read)
- result = []
- if role["run_list"]
- dependencies_from_runlist(role["run_list"]).each do |dependency|
- result << dependency if !result.include?(dependency)
- end
+ if entry.parent && entry.parent.path == "/cookbooks"
+ entry.chef_object.metadata.dependencies.keys.map { |cookbook| "/cookbooks/#{cookbook}" }
+
+ elsif entry.parent && entry.parent.path == "/nodes"
+ node = Chef::JSONCompat.parse(entry.read)
+ result = []
+ if node["chef_environment"] && node["chef_environment"] != "_default"
+ result << "/environments/#{node["chef_environment"]}.json"
+ end
+ if node["run_list"]
+ result += dependencies_from_runlist(node["run_list"])
+ end
+ result
+
+ elsif entry.parent && entry.parent.path == "/roles"
+ role = Chef::JSONCompat.parse(entry.read)
+ result = []
+ if role["run_list"]
+ dependencies_from_runlist(role["run_list"]).each do |dependency|
+ result << dependency unless result.include?(dependency)
end
- if role["env_run_lists"]
- role["env_run_lists"].each_pair do |env, run_list|
- dependencies_from_runlist(run_list).each do |dependency|
- result << dependency if !result.include?(dependency)
- end
+ end
+ 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 unless result.include?(dependency)
end
end
- result
+ end
+ result
- elsif !entry.exists?
- raise Chef::ChefFS::FileSystem::NotFoundError.new(entry)
+ elsif !entry.exists?
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(entry)
- else
- []
- end
- rescue Chef::ChefFS::FileSystem::NotFoundError => e
- ui.error "#{format_path(e.entry)}: No such file or directory"
- self.exit_code = 2
+ else
[]
end
+ rescue Chef::ChefFS::FileSystem::NotFoundError => e
+ ui.error "#{format_path(e.entry)}: No such file or directory"
+ self.exit_code = 2
+ []
end
def dependencies_from_runlist(run_list)
diff --git a/lib/chef/knife/diff.rb b/lib/chef/knife/diff.rb
index d965490f0a..3e9336aacc 100644
--- a/lib/chef/knife/diff.rb
+++ b/lib/chef/knife/diff.rb
@@ -1,4 +1,20 @@
-require "chef/chef_fs/knife"
+#
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../chef_fs/knife"
class Chef
class Knife
@@ -8,33 +24,32 @@ class Chef
category "path-based"
deps do
- require "chef/chef_fs/command_line"
+ require_relative "../chef_fs/command_line"
end
option :recurse,
- :long => "--[no-]recurse",
- :boolean => true,
- :default => true,
- :description => "List directories recursively."
+ long: "--[no-]recurse",
+ boolean: true,
+ default: true,
+ description: "List directories recursively."
option :name_only,
- :long => "--name-only",
- :boolean => true,
- :description => "Only show names of modified files."
+ long: "--name-only",
+ boolean: true,
+ description: "Only show names of modified files."
option :name_status,
- :long => "--name-status",
- :boolean => true,
- :description => "Only show names and statuses of modified files: Added, Deleted, Modified, and Type Changed."
+ long: "--name-status",
+ boolean: true,
+ description: "Only show names and statuses of modified files: Added, Deleted, Modified, and Type Changed."
option :diff_filter,
- :long => "--diff-filter=[(A|D|M|T)...[*]]",
- :description => "Select only files that are Added (A), Deleted (D), Modified (M), or have their type (i.e. regular file, directory) changed (T). Any combination of the filter characters (including none) can be used. When * (All-or-none) is added to the combination, all paths are selected if
- there is any file that matches other criteria in the comparison; if there is no file that matches other criteria, nothing is selected."
+ 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 ac8420d468..ab8c92a1c0 100644
--- a/lib/chef/knife/download.rb
+++ b/lib/chef/knife/download.rb
@@ -1,4 +1,20 @@
-require "chef/chef_fs/knife"
+#
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../chef_fs/knife"
class Chef
class Knife
@@ -8,43 +24,43 @@ class Chef
category "path-based"
deps do
- require "chef/chef_fs/command_line"
+ require_relative "../chef_fs/command_line"
end
option :recurse,
- :long => "--[no-]recurse",
- :boolean => true,
- :default => true,
- :description => "List directories recursively."
+ long: "--[no-]recurse",
+ boolean: true,
+ default: true,
+ description: "List directories recursively."
option :purge,
- :long => "--[no-]purge",
- :boolean => true,
- :default => false,
- :description => "Delete matching local files and directories that do not exist remotely."
+ long: "--[no-]purge",
+ boolean: true,
+ default: false,
+ description: "Delete matching local files and directories that do not exist remotely."
option :force,
- :long => "--[no-]force",
- :boolean => true,
- :default => false,
- :description => "Force upload of files even if they match (quicker and harmless, but doesn't print out what it changed)"
+ long: "--[no-]force",
+ boolean: true,
+ default: false,
+ description: "Force download of files even if they match (quicker and harmless, but doesn't print out what it changed)."
option :dry_run,
- :long => "--dry-run",
- :short => "-n",
- :boolean => true,
- :default => false,
- :description => "Don't take action, only print what would happen"
+ long: "--dry-run",
+ short: "-n",
+ boolean: true,
+ default: false,
+ description: "Don't take action, only print what would happen."
option :diff,
- :long => "--[no-]diff",
- :boolean => true,
- :default => true,
- :description => "Turn off to avoid uploading existing files; only new (and possibly deleted) files with --no-diff"
+ long: "--[no-]diff",
+ boolean: true,
+ default: true,
+ description: "Turn off to avoid downloading 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 8489e4e179..caca201566 100644
--- a/lib/chef/knife/edit.rb
+++ b/lib/chef/knife/edit.rb
@@ -1,4 +1,20 @@
-require "chef/chef_fs/knife"
+#
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../chef_fs/knife"
class Chef
class Knife
@@ -8,14 +24,14 @@ class Chef
category "path-based"
deps do
- require "chef/chef_fs/file_system"
- require "chef/chef_fs/file_system/not_found_error"
+ require_relative "../chef_fs/file_system"
+ require_relative "../chef_fs/file_system/exceptions"
end
option :local,
- :long => "--local",
- :boolean => true,
- :description => "Show local files instead of remote"
+ long: "--local",
+ boolean: true,
+ description: "Show local files instead of remote."
def run
# Get the matches (recursively)
@@ -50,15 +66,15 @@ class Chef
end
def edit_text(text, extension)
- if !config[:disable_editing]
+ unless config[:disable_editing]
Tempfile.open([ "knife-edit-", extension ]) do |file|
# Write the text to a temporary file
file.write(text)
file.close
# Let the user edit the temporary file
- if !system("#{config[:editor]} #{file.path}")
- raise "Please set EDITOR environment variable. See https://docs.chef.io/knife_using.html for details."
+ unless system("#{config[:editor]} #{file.path}")
+ raise "Please set EDITOR environment variable. See https://docs.chef.io/knife_setup/ for details."
end
result_text = IO.read(file.path)
diff --git a/lib/chef/knife/environment_compare.rb b/lib/chef/knife/environment_compare.rb
index 8a2ef853d3..22abee59c8 100644
--- a/lib/chef/knife/environment_compare.rb
+++ b/lib/chef/knife/environment_compare.rb
@@ -16,29 +16,29 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class EnvironmentCompare < Knife
deps do
- require "chef/environment"
+ require_relative "../environment"
end
banner "knife environment compare [ENVIRONMENT..] (options)"
option :all,
- :short => "-a",
- :long => "--all",
- :description => "Show all cookbooks",
- :boolean => true
+ short: "-a",
+ long: "--all",
+ description: "Show all cookbooks.",
+ boolean: true
option :mismatch,
- :short => "-m",
- :long => "--mismatch",
- :description => "Only show mismatching versions",
- :boolean => true
+ short: "-m",
+ long: "--mismatch",
+ description: "Only show mismatching versions.",
+ boolean: true
def run
# Get the commandline environments or all if none are provided.
@@ -81,7 +81,7 @@ class Chef
def constraint_list(environments)
constraints = {}
- environments.each do |env, url|
+ environments.each do |env, url| # rubocop:disable Style/HashEachMethods
# Because you cannot modify the default environment I filter it out here.
unless env == "_default"
envdata = Chef::Environment.load(env)
@@ -94,21 +94,22 @@ class Chef
def cookbook_list(constraints)
result = {}
- constraints.each { |env, cb| result.merge!(cb) }
+ constraints.each_value { |cb| result.merge!(cb) }
result
end
def matrix_output(cookbooks, constraints)
rows = [ "" ]
environments = []
- constraints.each { |e, v| environments << e.to_s }
+ constraints.each_key { |e| environments << e.to_s }
columns = environments.count + 1
environments.each { |env| rows << ui.color(env, :bold) }
- cookbooks.each do |c, v|
+ cookbooks.each_key do |c|
total = []
environments.each { |n| total << constraints[n][c] }
if total.uniq.count == 1
next if config[:mismatch]
+
color = :white
else
color = :yellow
diff --git a/lib/chef/knife/environment_create.rb b/lib/chef/knife/environment_create.rb
index cfc1bc268c..a724f72d4f 100644
--- a/lib/chef/knife/environment_create.rb
+++ b/lib/chef/knife/environment_create.rb
@@ -1,6 +1,6 @@
#
# Author:: Stephen Delano (<stephen@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,23 +16,22 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class EnvironmentCreate < Knife
deps do
- require "chef/environment"
- require "chef/json_compat"
+ require_relative "../environment"
end
banner "knife environment create ENVIRONMENT (options)"
option :description,
- :short => "-d DESCRIPTION",
- :long => "--description DESCRIPTION",
- :description => "The environment description"
+ short: "-d DESCRIPTION",
+ long: "--description DESCRIPTION",
+ description: "The environment description."
def run
env_name = @name_args[0]
diff --git a/lib/chef/knife/environment_delete.rb b/lib/chef/knife/environment_delete.rb
index 869d1c74fe..ec1b7cb8d8 100644
--- a/lib/chef/knife/environment_delete.rb
+++ b/lib/chef/knife/environment_delete.rb
@@ -1,6 +1,6 @@
#
# Author:: Stephen Delano (<stephen@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,15 +16,14 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class EnvironmentDelete < Knife
deps do
- require "chef/environment"
- require "chef/json_compat"
+ require_relative "../environment"
end
banner "knife environment delete ENVIRONMENT (options)"
diff --git a/lib/chef/knife/environment_edit.rb b/lib/chef/knife/environment_edit.rb
index 43f0b067ae..7c6105a6c0 100644
--- a/lib/chef/knife/environment_edit.rb
+++ b/lib/chef/knife/environment_edit.rb
@@ -1,6 +1,6 @@
#
# Author:: Stephen Delano (<stephen@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,15 +16,14 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class EnvironmentEdit < Knife
deps do
- require "chef/environment"
- require "chef/json_compat"
+ require_relative "../environment"
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 5272c8934a..a5011a3abf 100644
--- a/lib/chef/knife/environment_from_file.rb
+++ b/lib/chef/knife/environment_from_file.rb
@@ -1,6 +1,6 @@
#
# Author:: Stephen Delano (<stephen@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,21 +16,23 @@
# limitations under the License.
#
+require_relative "../knife"
+
class Chef
class Knife
class EnvironmentFromFile < Knife
deps do
- require "chef/environment"
- require "chef/knife/core/object_loader"
+ require_relative "../environment"
+ require_relative "core/object_loader"
end
banner "knife environment from file FILE [FILE..] (options)"
option :all,
- :short => "-a",
- :long => "--all",
- :description => "Upload all environments"
+ short: "-a",
+ long: "--all",
+ description: "Upload all environments."
def loader
@loader ||= Knife::Core::ObjectLoader.new(Chef::Environment, ui)
diff --git a/lib/chef/knife/environment_list.rb b/lib/chef/knife/environment_list.rb
index f278046bf9..7bcdeb6084 100644
--- a/lib/chef/knife/environment_list.rb
+++ b/lib/chef/knife/environment_list.rb
@@ -1,6 +1,6 @@
#
# Author:: Stephen Delano (<stephen@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,23 +16,22 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class EnvironmentList < Knife
deps do
- require "chef/environment"
- require "chef/json_compat"
+ require_relative "../environment"
end
banner "knife environment list (options)"
option :with_uri,
- :short => "-w",
- :long => "--with-uri",
- :description => "Show corresponding URIs"
+ short: "-w",
+ long: "--with-uri",
+ description: "Show corresponding URIs."
def run
output(format_list_for_display(Chef::Environment.list))
diff --git a/lib/chef/knife/environment_show.rb b/lib/chef/knife/environment_show.rb
index 6d260adbd6..e336b2d392 100644
--- a/lib/chef/knife/environment_show.rb
+++ b/lib/chef/knife/environment_show.rb
@@ -1,6 +1,6 @@
#
# Author:: Stephen Delano (<stephen@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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"
+require_relative "../knife"
class Chef
class Knife
@@ -25,8 +25,7 @@ class Chef
include Knife::Core::MultiAttributeReturnOption
deps do
- require "chef/environment"
- require "chef/json_compat"
+ require_relative "../environment"
end
banner "knife environment show ENVIRONMENT (options)"
diff --git a/lib/chef/knife/exec.rb b/lib/chef/knife/exec.rb
index 0aa8ea2ba8..d3ce2cee24 100644
--- a/lib/chef/knife/exec.rb
+++ b/lib/chef/knife/exec.rb
@@ -1,6 +1,6 @@
#--
# Author:: Daniel DeLeo (<dan@chef.io)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,30 +16,34 @@
# limitations under the License.
#
-require "chef/knife"
-require "chef/util/path_helper"
+require_relative "../knife"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef::Knife::Exec < Chef::Knife
banner "knife exec [SCRIPT] (options)"
+ deps do
+ require_relative "../util/path_helper"
+ end
+
option :exec,
- :short => "-E CODE",
- :long => "--exec CODE",
- :description => "a string of Chef code to execute"
+ short: "-E CODE",
+ long: "--exec CODE",
+ description: "A string of #{ChefUtils::Dist::Infra::PRODUCT} code to execute."
option :script_path,
- :short => "-p PATH:PATH",
- :long => "--script-path PATH:PATH",
- :description => "A colon-separated path to look for scripts in",
- :proc => lambda { |o| o.split(":") }
+ short: "-p PATH:PATH",
+ long: "--script-path PATH:PATH",
+ description: "A colon-separated path to look for scripts in.",
+ proc: lambda { |o| o.split(":") }
deps do
- require "chef/shell/ext"
+ require_relative "../shell/ext"
end
def run
- config[:script_path] ||= Array(Chef::Config[:script_path])
+ config[:script_path] = Array(config[:script_path] || 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
@@ -56,6 +60,14 @@ class Chef::Knife::Exec < Chef::Knife
context.instance_eval(IO.read(file), file, 0)
end
else
+ puts "An interactive shell is opened"
+ puts
+ puts "Type your script and do:"
+ puts
+ puts "1. To run the script, use 'Ctrl D'"
+ puts "2. To exit, use 'Ctrl/Shift C'"
+ puts
+ puts "Type here a script..."
script = STDIN.read
context.instance_eval(script, "STDIN", 0)
end
@@ -64,19 +76,19 @@ class Chef::Knife::Exec < Chef::Knife
def find_script(x)
# Try to find a script. First try expanding the path given.
script = File.expand_path(x)
- return script if File.exists?(script)
+ return script if File.exist?(script)
# Failing that, try searching the script path. If we can't find
# anything, fail gracefully.
- Chef::Log.debug("Searching script_path: #{config[:script_path].inspect}")
+ Chef::Log.trace("Searching script_path: #{config[:script_path].inspect}")
config[:script_path].each do |path|
path = File.expand_path(path)
test = File.join(path, x)
- Chef::Log.debug("Testing: #{test}")
- if File.exists?(test)
+ Chef::Log.trace("Testing: #{test}")
+ if File.exist?(test)
script = test
- Chef::Log.debug("Found: #{test}")
+ Chef::Log.trace("Found: #{test}")
return script
end
end
diff --git a/lib/chef/knife/group_add.rb b/lib/chef/knife/group_add.rb
new file mode 100644
index 0000000000..eccb7dd10c
--- /dev/null
+++ b/lib/chef/knife/group_add.rb
@@ -0,0 +1,55 @@
+#
+# Author:: Seth Falcon (<seth@chef.io>)
+# Author:: Jeremiah Snapp (<jeremiah@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../knife"
+
+class Chef
+ class Knife
+ class GroupAdd < Chef::Knife
+ category "group"
+ banner "knife group add MEMBER_TYPE MEMBER_NAME GROUP_NAME"
+
+ deps do
+ require_relative "acl_base"
+ include Chef::Knife::AclBase
+ end
+
+ def run
+ member_type, member_name, group_name = name_args
+
+ if name_args.length != 3
+ show_usage
+ ui.fatal "You must specify member type [client|group|user], member name and group name"
+ exit 1
+ end
+
+ validate_member_name!(group_name)
+ validate_member_type!(member_type)
+ validate_member_name!(member_name)
+
+ if group_name.downcase == "users"
+ ui.fatal "knife group can not manage members of Chef Infra Server's 'users' group, which contains all users."
+ exit 1
+ end
+
+ add_to_group!(member_type, member_name, group_name)
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/osc_user_show.rb b/lib/chef/knife/group_create.rb
index 22e9bf4dcd..4219188951 100644
--- a/lib/chef/knife/osc_user_show.rb
+++ b/lib/chef/knife/group_create.rb
@@ -1,6 +1,7 @@
#
-# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Author:: Seth Falcon (<seth@chef.io>)
+# Author:: Jeremiah Snapp (<jeremiah@chef.io>)
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,39 +17,33 @@
# 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.
+require_relative "../knife"
class Chef
class Knife
- class OscUserShow < Knife
-
- include Knife::Core::MultiAttributeReturnOption
+ class GroupCreate < Chef::Knife
+ category "group"
+ banner "knife group create GROUP_NAME"
deps do
- require "chef/user"
- require "chef/json_compat"
+ require_relative "acl_base"
+ include Chef::Knife::AclBase
end
- banner "knife osc_user show USER (options)"
-
def run
- @user_name = @name_args[0]
+ group_name = name_args[0]
- if @user_name.nil?
+ if name_args.length != 1
show_usage
- ui.fatal("You must specify a user name")
+ ui.fatal "You must specify group name"
exit 1
end
- user = Chef::User.load(@user_name)
- output(format_for_display(user))
- end
+ validate_member_name!(group_name)
+ ui.msg "Creating '#{group_name}' group"
+ rest.post_rest("groups", { groupname: group_name })
+ end
end
end
end
diff --git a/lib/chef/knife/group_destroy.rb b/lib/chef/knife/group_destroy.rb
new file mode 100644
index 0000000000..433a5cc627
--- /dev/null
+++ b/lib/chef/knife/group_destroy.rb
@@ -0,0 +1,53 @@
+#
+# Author:: Christopher Maier (<cm@chef.io>)
+# Author:: Jeremiah Snapp (<jeremiah@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../knife"
+
+class Chef
+ class Knife
+ class GroupDestroy < Chef::Knife
+ category "group"
+ banner "knife group destroy GROUP_NAME"
+
+ deps do
+ require_relative "acl_base"
+ include Chef::Knife::AclBase
+ end
+
+ def run
+ group_name = name_args[0]
+
+ if name_args.length != 1
+ show_usage
+ ui.fatal "You must specify group name"
+ exit 1
+ end
+
+ validate_member_name!(group_name)
+
+ if %w{admins billing-admins clients users}.include?(group_name.downcase)
+ ui.fatal "The '#{group_name}' group is a special group that cannot not be destroyed"
+ exit 1
+ end
+ ui.msg "Destroying '#{group_name}' group"
+ rest.delete_rest("groups/#{group_name}")
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/git.rb b/lib/chef/knife/group_list.rb
index 4799b54d3d..fc8f00ad6d 100644
--- a/lib/chef/resource/git.rb
+++ b/lib/chef/knife/group_list.rb
@@ -1,6 +1,7 @@
#
-# Author:: Daniel DeLeo (<dan@kallistec.com>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Author:: Seth Falcon (<seth@chef.io>)
+# Author:: Jeremiah Snapp (<jeremiah@chef.io>)
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,29 +17,27 @@
# limitations under the License.
#
-require "chef/resource/scm"
+require_relative "../knife"
class Chef
- class Resource
- class Git < Chef::Resource::Scm
+ class Knife
+ class GroupList < Chef::Knife
+ category "group"
+ banner "knife group list"
- def initialize(name, run_context = nil)
- super
- @additional_remotes = Hash[]
+ deps do
+ require_relative "acl_base"
+ include Chef::Knife::AclBase
end
- def additional_remotes(arg = nil)
- set_or_return(
- :additional_remotes,
- arg,
- :kind_of => Hash
- )
+ def run
+ groups = rest.get_rest("groups").keys.sort
+ ui.output(remove_usags(groups))
end
- alias :branch :revision
- alias :reference :revision
-
- alias :repo :repository
+ def remove_usags(groups)
+ groups.select { |gname| !is_usag?(gname) }
+ end
end
end
end
diff --git a/lib/chef/knife/group_remove.rb b/lib/chef/knife/group_remove.rb
new file mode 100644
index 0000000000..07ab19693f
--- /dev/null
+++ b/lib/chef/knife/group_remove.rb
@@ -0,0 +1,56 @@
+#
+# Author:: Seth Falcon (<seth@chef.io>)
+# Author:: Jeremiah Snapp (<jeremiah@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../knife"
+
+class Chef
+ class Knife
+ class GroupRemove < Chef::Knife
+ category "group"
+ banner "knife group remove MEMBER_TYPE MEMBER_NAME GROUP_NAME"
+
+ deps do
+ require_relative "acl_base"
+ include Chef::Knife::AclBase
+ end
+
+ def run
+ member_type, member_name, group_name = name_args
+
+ if name_args.length != 3
+ show_usage
+ ui.fatal "You must specify member type [client|group|user], member name and group name"
+ exit 1
+ end
+
+ validate_member_name!(group_name)
+ validate_member_type!(member_type)
+ validate_member_name!(member_name)
+
+ if group_name.downcase == "users"
+ ui.fatal "knife-acl can not manage members of the Users group"
+ ui.fatal "please read knife-acl's README.md for more information"
+ exit 1
+ end
+
+ remove_from_group!(member_type, member_name, group_name)
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/group_show.rb b/lib/chef/knife/group_show.rb
new file mode 100644
index 0000000000..6ac53f6b6e
--- /dev/null
+++ b/lib/chef/knife/group_show.rb
@@ -0,0 +1,49 @@
+#
+# Author:: Seth Falcon (<seth@chef.io>)
+# Author:: Jeremiah Snapp (<jeremiah@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../knife"
+
+class Chef
+ class Knife
+ class GroupShow < Chef::Knife
+ category "group"
+ banner "knife group show GROUP_NAME"
+
+ deps do
+ require_relative "acl_base"
+ include Chef::Knife::AclBase
+ end
+
+ def run
+ group_name = name_args[0]
+
+ if name_args.length != 1
+ show_usage
+ ui.fatal "You must specify group name"
+ exit 1
+ end
+
+ validate_member_name!(group_name)
+
+ group = rest.get_rest("groups/#{group_name}")
+ ui.output group
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/help.rb b/lib/chef/knife/help.rb
deleted file mode 100644
index e45b54eec8..0000000000
--- a/lib/chef/knife/help.rb
+++ /dev/null
@@ -1,101 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2011-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.
-#
-
-class Chef
- class Knife
- class Help < Chef::Knife
-
- banner "knife help [list|TOPIC]"
-
- def run
- if name_args.empty?
- ui.info "Usage: knife SUBCOMMAND (options)"
- ui.msg ""
- # This command is atypical, the user is likely not interested in usage of
- # this command, but knife in general. So hack the banner.
- opt_parser.banner = "General Knife Options:"
- ui.msg opt_parser.to_s
- ui.msg ""
- ui.info "For further help:"
- ui.info(<<-MOAR_HELP)
- knife help list list help topics
- knife help knife show general knife help
- knife help TOPIC display the manual for TOPIC
- knife SUBCOMMAND --help show the options for a command
-MOAR_HELP
- exit 1
- else
- @query = name_args.join("-")
- end
-
- case @query
- when "topics", "list"
- print_help_topics
- exit 1
- when "intro", "knife"
- @topic = "knife"
- else
- @topic = find_manpages_for_query(@query)
- end
-
- manpage_path = find_manpage_path(@topic)
- exec "man #{manpage_path}"
- end
-
- def help_topics
- # The list of help topics is generated by a rake task from the available man pages
- # This constant is provided in help_topics.rb which is automatically required/loaded by the knife subcommand loader.
- HELP_TOPICS
- end
-
- def print_help_topics
- ui.info "Available help topics are: "
- help_topics.collect { |t| t.gsub(/knife-/, "") }.sort.each do |topic|
- ui.msg " #{topic}"
- end
- end
-
- def find_manpages_for_query(query)
- possibilities = help_topics.select do |manpage|
- ::File.fnmatch("knife-#{query}*", manpage) || ::File.fnmatch("#{query}*", manpage)
- end
- if possibilities.empty?
- ui.error "No help found for '#{query}'"
- ui.msg ""
- print_help_topics
- exit 1
- elsif possibilities.size == 1
- possibilities.first
- else
- ui.info "Multiple help topics match your query. Pick one:"
- ui.highline.choose(*possibilities)
- end
- end
-
- def find_manpage_path(topic)
- if ::File.exists?(::File.expand_path("../distro/common/man/man1/#{topic}.1", CHEF_ROOT))
- # If we've provided the man page in the gem, give that
- return ::File.expand_path("../distro/common/man/man1/#{topic}.1", CHEF_ROOT)
- else
- # Otherwise, we'll just be using MANPATH
- topic
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/help_topics.rb b/lib/chef/knife/help_topics.rb
deleted file mode 100644
index a2aad65f55..0000000000
--- a/lib/chef/knife/help_topics.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-# Do not edit this file by hand
-# This file is autogenerated by the docs:list rake task from the available manpages
-
-HELP_TOPICS = ["chef-shell", "knife-bootstrap", "knife-client", "knife-configure", "knife-cookbook-site", "knife-cookbook", "knife-data-bag", "knife-delete", "knife-deps", "knife-diff", "knife-download", "knife-edit", "knife-environment", "knife-exec", "knife-index-rebuild", "knife-list", "knife-node", "knife-raw", "knife-recipe-list", "knife-role", "knife-search", "knife-show", "knife-ssh", "knife-status", "knife-tag", "knife-upload", "knife-user", "knife-xargs", "knife"]
diff --git a/lib/chef/knife/index_rebuild.rb b/lib/chef/knife/index_rebuild.rb
deleted file mode 100644
index 206b7b0fbf..0000000000
--- a/lib/chef/knife/index_rebuild.rb
+++ /dev/null
@@ -1,133 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@kallistec.com>)
-# Copyright:: Copyright 2009-2016, Daniel DeLeo
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require "chef/knife"
-
-class Chef
- class Knife
- class IndexRebuild < Knife
-
- banner "knife index rebuild (options)"
- option :yes,
- :short => "-y",
- :long => "--yes",
- :boolean => true,
- :description => "don't bother to ask if I'm sure"
-
- def run
- api_info = grab_api_info
-
- if unsupported_version?(api_info)
- unsupported_server_message(api_info)
- exit 1
- else
- deprecated_server_message
- nag
- output rest.post("/search/reindex", {})
- end
- end
-
- def grab_api_info
- # Since we don't yet have any endpoints that implement an
- # OPTIONS handler, we need to get our version header
- # information in a more roundabout way. We'll try to query
- # for a node we know won't exist; the 404 response that comes
- # back will give us what we want
- dummy_node = "knife_index_rebuild_test_#{rand(1000000)}"
- rest.get("/nodes/#{dummy_node}")
- rescue Net::HTTPServerException => exception
- r = exception.response
- parse_api_info(r)
- end
-
- # Only Chef 11+ servers will have version information in their
- # headers, and only those servers will lack an API endpoint for
- # index rebuilding.
- def unsupported_version?(api_info)
- !!api_info["version"]
- end
-
- def unsupported_server_message(api_info)
- ui.error("Rebuilding the index is not available via knife for #{server_type(api_info)}s version 11.0.0 and above.")
- ui.info("Instead, run the '#{ctl_command(api_info)} reindex' command on the server itself.")
- end
-
- def deprecated_server_message
- ui.warn("'knife index rebuild' has been removed for Chef 11+ servers. It will continue to work for prior versions, however.")
- end
-
- def nag
- ui.info("This operation is destructive. Rebuilding the index may take some time.")
- ui.confirm("Continue")
- end
-
- # Chef 11 (and above) servers return various pieces of
- # information about the server in an +x-ops-api-info+ header.
- # This is a +;+ delimited string of key / value pairs, separated
- # by +=+.
- #
- # Given a Net::HTTPResponse object, this method extracts this
- # information (if present), and returns it as a hash. If no
- # such header is found, an empty hash is returned.
- def parse_api_info(response)
- value = response["x-ops-api-info"]
- if value
- kv = value.split(";")
- kv.inject({}) do |acc, pair|
- k, v = pair.split("=")
- acc[k] = v
- acc
- end
- else
- {}
- end
- end
-
- # Given an API info hash (see +#parse_api_info(response)+),
- # return a string describing the kind of server we're
- # interacting with (based on the +flavor+ field)
- def server_type(api_info)
- case api_info["flavor"]
- when "osc"
- "Open Source Chef Server"
- when "opc"
- "Private Chef Server"
- else
- # Generic fallback
- "Chef Server"
- end
- end
-
- # Given an API info hash (see +#parse_api_info(response)+),
- # return the name of the "server-ctl" command for the kind of
- # server we're interacting with (based on the +flavor+ field)
- def ctl_command(api_info)
- case api_info["flavor"]
- when "osc"
- "chef-server-ctl"
- when "opc"
- "private-chef-ctl"
- else
- # Generic fallback
- "chef-server-ctl"
- end
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/key_create.rb b/lib/chef/knife/key_create.rb
index a9f9da97a7..6129cab683 100644
--- a/lib/chef/knife/key_create.rb
+++ b/lib/chef/knife/key_create.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/key"
-require "chef/json_compat"
-require "chef/exceptions"
+require_relative "../key"
+require_relative "../json_compat"
+require_relative "../exceptions"
class Chef
class Knife
@@ -40,11 +40,11 @@ class Chef
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
+ <<~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)
diff --git a/lib/chef/knife/key_create_base.rb b/lib/chef/knife/key_create_base.rb
index d02d5ee180..a1d658e43c 100644
--- a/lib/chef/knife/key_create_base.rb
+++ b/lib/chef/knife/key_create_base.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,24 +25,24 @@ class Chef
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."
+ 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."
+ 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."
+ 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."
+ short: "-e DATE",
+ long: "--expiration-date DATE",
+ description: "Optionally pass the expiration date for the key in ISO 8601 formatted 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
diff --git a/lib/chef/knife/key_delete.rb b/lib/chef/knife/key_delete.rb
index a798e06475..10f1235924 100644
--- a/lib/chef/knife/key_delete.rb
+++ b/lib/chef/knife/key_delete.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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/key"
+require_relative "../key"
class Chef
class Knife
diff --git a/lib/chef/knife/key_edit.rb b/lib/chef/knife/key_edit.rb
index 8490d10fa5..3f8918f1a9 100644
--- a/lib/chef/knife/key_edit.rb
+++ b/lib/chef/knife/key_edit.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/key"
-require "chef/json_compat"
-require "chef/exceptions"
+require_relative "../key"
+require_relative "../json_compat"
+require_relative "../exceptions"
class Chef
class Knife
@@ -41,12 +41,12 @@ class Chef
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
+ <<~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)
diff --git a/lib/chef/knife/key_edit_base.rb b/lib/chef/knife/key_edit_base.rb
index 1a613ef1bc..b094877190 100644
--- a/lib/chef/knife/key_edit_base.rb
+++ b/lib/chef/knife/key_edit_base.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,29 +25,29 @@ class Chef
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."
+ 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."
+ 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."
+ 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."
+ 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."
+ short: "-e DATE",
+ long: "--expiration-date DATE",
+ description: "Updates the expiration_date field of your key if passed. Pass in ISO 8601 formatted string: YYYY-MM-DDTHH:MM:SSZ e.g. 2013-12-24T21:00:00Z or infinity. UTC timezone assumed."
end
end
end
diff --git a/lib/chef/knife/key_list.rb b/lib/chef/knife/key_list.rb
index 9a820b7a50..076b39d251 100644
--- a/lib/chef/knife/key_list.rb
+++ b/lib/chef/knife/key_list.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/key"
-require "chef/json_compat"
-require "chef/exceptions"
+require_relative "../key"
+require_relative "../json_compat"
+require_relative "../exceptions"
class Chef
class Knife
@@ -40,10 +40,10 @@ class Chef
end
def expired_and_non_expired_msg
- <<EOS
-You cannot pass both --only-expired and --only-non-expired.
-Please pass one or none.
-EOS
+ <<~EOS
+ You cannot pass both --only-expired and --only-non-expired.
+ Please pass one or none.
+ EOS
end
def display_info(string)
@@ -70,7 +70,8 @@ EOS
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 = "#{colorize(key["name"].ljust(max_length))} #{key["uri"]}"
display = "#{display} (expired)" if key["expired"]
display_info(display)
end
@@ -78,6 +79,7 @@ EOS
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
diff --git a/lib/chef/knife/key_list_base.rb b/lib/chef/knife/key_list_base.rb
index 95858e9ba1..e06e908b69 100644
--- a/lib/chef/knife/key_list_base.rb
+++ b/lib/chef/knife/key_list_base.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,19 +25,19 @@ class Chef
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."
+ 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."
+ 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."
+ short: "-n",
+ long: "--only-non-expired",
+ description: "Only show non-expired keys."
end
end
end
diff --git a/lib/chef/knife/key_show.rb b/lib/chef/knife/key_show.rb
index 851db7208b..8b3d980004 100644
--- a/lib/chef/knife/key_show.rb
+++ b/lib/chef/knife/key_show.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/key"
-require "chef/json_compat"
-require "chef/exceptions"
+require_relative "../key"
+require_relative "../json_compat"
+require_relative "../exceptions"
class Chef
class Knife
diff --git a/lib/chef/knife/list.rb b/lib/chef/knife/list.rb
index fcfde0eb45..1cc398e01a 100644
--- a/lib/chef/knife/list.rb
+++ b/lib/chef/knife/list.rb
@@ -1,42 +1,63 @@
-require "chef/chef_fs/knife"
+#
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../chef_fs/knife"
class Chef
class Knife
class List < Chef::ChefFS::Knife
- banner "knife list [-dfR1p] [PATTERN1 ... PATTERNn]"
+ banner "knife list [-dfR1p] [PATTERN1 ... PATTERNn] (options)"
category "path-based"
deps do
- require "chef/chef_fs/file_system"
- require "highline"
+ require_relative "../chef_fs/file_system"
+ require "tty-screen"
end
option :recursive,
- :short => "-R",
- :boolean => true,
- :description => "List directories recursively"
+ short: "-R",
+ boolean: true,
+ description: "List directories recursively."
+
option :bare_directories,
- :short => "-d",
- :boolean => true,
- :description => "When directories match the pattern, do not show the directories' children"
+ short: "-d",
+ boolean: true,
+ description: "When directories match the pattern, do not show the directories' children."
+
option :local,
- :long => "--local",
- :boolean => true,
- :description => "List local directory instead of remote"
+ long: "--local",
+ boolean: true,
+ description: "List local directory instead of remote."
+
option :flat,
- :short => "-f",
- :long => "--flat",
- :boolean => true,
- :description => "Show a list of filenames rather than the prettified ls-like output normally produced"
+ short: "-f",
+ long: "--flat",
+ boolean: true,
+ description: "Show a list of filenames rather than the prettified ls-like output normally produced."
+
option :one_column,
- :short => "-1",
- :boolean => true,
- :description => "Show only one column of results"
+ short: "-1",
+ boolean: true,
+ description: "Show only one column of results."
+
option :trailing_slashes,
- :short => "-p",
- :boolean => true,
- :description => "Show trailing slashes after directories"
+ short: "-p",
+ boolean: true,
+ description: "Show trailing slashes after directories."
attr_accessor :exit_code
@@ -56,7 +77,7 @@ class Chef
# Process directories
if !config[:bare_directories]
- dir_results = parallelize(all_results.select { |result| result.dir? }) do |result|
+ dir_results = parallelize(all_results.select(&:dir?)) do |result|
add_dir_result(result)
end.flatten(1)
@@ -69,14 +90,14 @@ class Chef
# Flatten out directory results if necessary
if config[:flat]
- dir_results.each do |result, children|
+ dir_results.each do |result, children| # rubocop:disable Style/HashEachMethods
results += children
end
dir_results = []
end
# Sort by path for happy output
- results = results.sort_by { |result| result.path }
+ results = results.sort_by(&:path)
dir_results = dir_results.sort_by { |result| result[0].path }
# Print!
@@ -97,12 +118,12 @@ class Chef
print_results(children.map { |result| maybe_add_slash(result.display_name, result.dir?) }.sort, "")
end
- exit self.exit_code if self.exit_code
+ exit exit_code if exit_code
end
def add_dir_result(result)
begin
- children = result.children.sort_by { |child| child.name }
+ children = result.children.sort_by(&:name)
rescue Chef::ChefFS::FileSystem::NotFoundError => e
ui.error "#{format_path(e.entry)}: No such file or directory"
return []
@@ -110,7 +131,7 @@ class Chef
result = [ [ result, children ] ]
if config[:recursive]
- child_dirs = children.select { |child| child.dir? }
+ child_dirs = children.select(&:dir?)
result += parallelize(child_dirs) { |child| add_dir_result(child) }.flatten(1).to_a
end
result
@@ -123,11 +144,11 @@ class Chef
def print_results(results, indent)
return if results.length == 0
- print_space = results.map { |result| result.length }.max + 2
+ print_space = results.map(&:length).max + 2
if config[:one_column] || !stdout.isatty
columns = 0
else
- columns = HighLine::SystemExtensions.terminal_size[0]
+ columns = TTY::Screen.columns
end
current_line = ""
results.each do |result|
diff --git a/lib/chef/knife/node_bulk_delete.rb b/lib/chef/knife/node_bulk_delete.rb
index 2ca63da512..874509b730 100644
--- a/lib/chef/knife/node_bulk_delete.rb
+++ b/lib/chef/knife/node_bulk_delete.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class NodeBulkDelete < Knife
deps do
- require "chef/node"
- require "chef/json_compat"
+ require_relative "../node"
+ require_relative "../json_compat"
end
banner "knife node bulk delete REGEX (options)"
@@ -39,7 +39,8 @@ class Chef
matcher = /#{name_args[0]}/
all_nodes.each do |name, node|
- next unless name =~ matcher
+ next unless name&.match?(matcher)
+
nodes_to_delete[name] = node
end
diff --git a/lib/chef/knife/node_create.rb b/lib/chef/knife/node_create.rb
index f80ac9e87c..c0db667b25 100644
--- a/lib/chef/knife/node_create.rb
+++ b/lib/chef/knife/node_create.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class NodeCreate < Knife
deps do
- require "chef/node"
- require "chef/json_compat"
+ require_relative "../node"
+ require_relative "../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 4dd7d764a1..7c0c6f0a21 100644
--- a/lib/chef/knife/node_delete.rb
+++ b/lib/chef/knife/node_delete.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,29 +16,29 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class NodeDelete < Knife
deps do
- require "chef/node"
- require "chef/json_compat"
+ require_relative "../node"
+ require_relative "../json_compat"
end
- banner "knife node delete NODE (options)"
+ banner "knife node delete [NODE [NODE]] (options)"
def run
- @node_name = @name_args[0]
-
- if @node_name.nil?
+ if @name_args.length == 0
show_usage
- ui.fatal("You must specify a node name")
+ ui.fatal("You must specify at least one node name")
exit 1
end
- delete_object(Chef::Node, @node_name)
+ @name_args.each do |node_name|
+ delete_object(Chef::Node, node_name)
+ end
end
end
diff --git a/lib/chef/knife/node_edit.rb b/lib/chef/knife/node_edit.rb
index 4632c0a5b4..a2585391ea 100644
--- a/lib/chef/knife/node_edit.rb
+++ b/lib/chef/knife/node_edit.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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"
+require_relative "../knife"
class Chef
class Knife
@@ -24,18 +24,18 @@ class Chef
class NodeEdit < Knife
deps do
- require "chef/node"
- require "chef/json_compat"
- require "chef/knife/core/node_editor"
+ require_relative "../node"
+ require_relative "../json_compat"
+ require_relative "core/node_editor"
end
banner "knife node edit NODE (options)"
option :all_attributes,
- :short => "-a",
- :long => "--all",
- :boolean => true,
- :description => "Display all attributes when editing"
+ short: "-a",
+ long: "--all",
+ boolean: true,
+ description: "Display all attributes when editing."
def run
if node_name.nil?
@@ -46,7 +46,7 @@ class Chef
updated_node = node_editor.edit_node
if updated_values = node_editor.updated?
- ui.info "Saving updated #{updated_values.join(', ')} on node #{node.name}"
+ ui.info "Saving updated #{updated_values.join(", ")} on node #{node.name}"
updated_node.save
else
ui.info "Node not updated, skipping node save"
diff --git a/lib/chef/knife/node_environment_set.rb b/lib/chef/knife/node_environment_set.rb
index ecba01be29..644b6138b6 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_relative "../knife"
class Chef
class Knife
class NodeEnvironmentSet < Knife
deps do
- require "chef/node"
+ require_relative "../node"
end
banner "knife node environment set NODE ENVIRONMENT"
@@ -44,8 +44,7 @@ class Chef
node.save
- config[:attribute] = "chef_environment"
-
+ config[:environment] = @environment
output(format_for_display(node))
end
diff --git a/lib/chef/knife/node_from_file.rb b/lib/chef/knife/node_from_file.rb
index 61b83edd92..86d602ae7c 100644
--- a/lib/chef/knife/node_from_file.rb
+++ b/lib/chef/knife/node_from_file.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,16 +16,16 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class NodeFromFile < Knife
deps do
- require "chef/node"
- require "chef/json_compat"
- require "chef/knife/core/object_loader"
+ require_relative "../node"
+ require_relative "../json_compat"
+ require_relative "core/object_loader"
end
banner "knife node from file FILE (options)"
diff --git a/lib/chef/knife/node_list.rb b/lib/chef/knife/node_list.rb
index 4885208136..a8b57aedc5 100644
--- a/lib/chef/knife/node_list.rb
+++ b/lib/chef/knife/node_list.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,23 +16,23 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class NodeList < Knife
deps do
- require "chef/node"
- require "chef/json_compat"
+ require_relative "../node"
+ require_relative "../json_compat"
end
banner "knife node list (options)"
option :with_uri,
- :short => "-w",
- :long => "--with-uri",
- :description => "Show corresponding URIs"
+ short: "-w",
+ long: "--with-uri",
+ description: "Show corresponding URIs."
def run
env = Chef::Config[:environment]
diff --git a/lib/chef/knife/node_policy_set.rb b/lib/chef/knife/node_policy_set.rb
new file mode 100644
index 0000000000..d34ebd9478
--- /dev/null
+++ b/lib/chef/knife/node_policy_set.rb
@@ -0,0 +1,79 @@
+#
+# Author:: Piyush Awasthi (<piyush.awasthi@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../knife"
+
+class Chef
+ class Knife
+ class NodePolicySet < Knife
+
+ deps do
+ require_relative "../node"
+ require_relative "../json_compat"
+ end
+
+ banner "knife node policy set NODE POLICY_GROUP POLICY_NAME (options)"
+
+ def run
+ validate_node!
+ validate_options!
+ node = Chef::Node.load(@name_args[0])
+ set_policy(node)
+ if node.save
+ ui.info "Successfully set the policy on node #{node.name}"
+ else
+ ui.info "Error in updating node #{node.name}"
+ end
+ end
+
+ private
+
+ # Set policy name and group to node
+ def set_policy(node)
+ policy_group, policy_name = @name_args[1..]
+ node.policy_name = policy_name
+ node.policy_group = policy_group
+ end
+
+ # Validate policy name and policy group
+ def validate_options!
+ if incomplete_policyfile_options?
+ ui.error("Policy group and name must be specified together")
+ exit 1
+ end
+ true
+ end
+
+ # Validate node pass in CLI
+ def validate_node!
+ if @name_args[0].nil?
+ ui.error("You must specify a node name")
+ show_usage
+ exit 1
+ end
+ end
+
+ # True if one of policy_name or policy_group was given, but not both
+ def incomplete_policyfile_options?
+ policy_group, policy_name = @name_args[1..]
+ (policy_group.nil? || policy_name.nil? || @name_args[1..-1].size > 2)
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/knife/node_run_list_add.rb b/lib/chef/knife/node_run_list_add.rb
index f8d40c8321..40476371eb 100644
--- a/lib/chef/knife/node_run_list_add.rb
+++ b/lib/chef/knife/node_run_list_add.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,39 +16,39 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class NodeRunListAdd < Knife
deps do
- require "chef/node"
- require "chef/json_compat"
+ require_relative "../node"
+ require_relative "../json_compat"
end
- banner "knife node run_list add [NODE] [ENTRY[,ENTRY]] (options)"
+ banner "knife node run_list add [NODE] [ENTRY [ENTRY]] (options)"
option :after,
- :short => "-a ITEM",
- :long => "--after ITEM",
- :description => "Place the ENTRY in the run list after ITEM"
+ short: "-a ITEM",
+ long: "--after ITEM",
+ description: "Place the ENTRY in the run list after ITEM."
option :before,
- :short => "-b ITEM",
- :long => "--before ITEM",
- :description => "Place the ENTRY in the run list before ITEM"
+ short: "-b ITEM",
+ long: "--before ITEM",
+ description: "Place the ENTRY in the run list before ITEM."
def run
node = Chef::Node.load(@name_args[0])
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 }
+ entries = @name_args[1..].map do |entry|
+ entry.split(",").map(&: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(&: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 3f9cdabfff..484e575475 100644
--- a/lib/chef/knife/node_run_list_remove.rb
+++ b/lib/chef/knife/node_run_list_remove.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,30 +16,30 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class NodeRunListRemove < Knife
deps do
- require "chef/node"
- require "chef/json_compat"
+ require_relative "../node"
+ require_relative "../json_compat"
end
- banner "knife node run_list remove [NODE] [ENTRY[,ENTRY]] (options)"
+ banner "knife node run_list remove [NODE] [ENTRY [ENTRY]] (options)"
def run
node = Chef::Node.load(@name_args[0])
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 }
+ entries = @name_args[1..].map do |entry|
+ entry.split(",").map(&: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(&:strip)
end
# iterate over the list of things to remove,
@@ -49,7 +49,7 @@ class Chef
node.run_list.remove(e)
else
ui.warn "#{e} is not in the run list"
- unless e =~ /^(recipe|role)\[/
+ unless /^(recipe|role)\[/.match?(e)
ui.warn "(did you forget recipe[] or role[] around it?)"
end
end
diff --git a/lib/chef/knife/node_run_list_set.rb b/lib/chef/knife/node_run_list_set.rb
index 2d68c88415..f356b39d95 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_relative "../knife"
class Chef
class Knife
class NodeRunListSet < Knife
deps do
- require "chef/node"
- require "chef/json_compat"
+ require_relative "../node"
+ require_relative "../json_compat"
end
banner "knife node run_list set NODE ENTRIES (options)"
@@ -36,12 +36,12 @@ class Chef
exit 1
elsif @name_args.size > 2
# Check for nested lists and create a single plain one
- entries = @name_args[1..-1].map do |entry|
- entry.split(",").map { |e| e.strip }
+ entries = @name_args[1..].map do |entry|
+ entry.split(",").map(&: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(&: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 c616b8ab72..173348dc41 100644
--- a/lib/chef/knife/node_show.rb
+++ b/lib/chef/knife/node_show.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,32 +16,34 @@
# limitations under the License.
#
-require "chef/knife"
-require "chef/knife/core/node_presenter"
+require_relative "../knife"
+require_relative "core/node_presenter"
+require_relative "core/formatting_options"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Knife
class NodeShow < Knife
- include Knife::Core::NodeFormattingOptions
+ include Knife::Core::FormattingOptions
include Knife::Core::MultiAttributeReturnOption
deps do
- require "chef/node"
- require "chef/json_compat"
+ require_relative "../node"
+ require_relative "../json_compat"
end
banner "knife node show NODE (options)"
option :run_list,
- :short => "-r",
- :long => "--run-list",
- :description => "Show only the run list"
+ short: "-r",
+ long: "--run-list",
+ description: "Show only the run list."
option :environment,
- :short => "-E",
- :long => "--environment",
- :description => "Show only the Chef environment"
+ short: "-E",
+ long: "--environment",
+ description: "Show only the #{ChefUtils::Dist::Infra::PRODUCT} environment."
def run
ui.use_presenter Knife::Core::NodePresenter
@@ -55,11 +57,6 @@ class Chef
node = Chef::Node.load(@node_name)
output(format_for_display(node))
- self.class.attrs_to_show = []
- end
-
- def self.attrs_to_show=(attrs)
- @attrs_to_show = attrs
end
end
end
diff --git a/lib/chef/knife/null.rb b/lib/chef/knife/null.rb
index 0b5058e8ea..7221eee9f5 100644
--- a/lib/chef/knife/null.rb
+++ b/lib/chef/knife/null.rb
@@ -3,8 +3,10 @@ class Chef
class Null < Chef::Knife
banner "knife null"
- def run
- end
+ # setting the category to deprecated keeps it out of help
+ category "deprecated"
+
+ def run; end
end
end
end
diff --git a/lib/chef/knife/osc_user_create.rb b/lib/chef/knife/osc_user_create.rb
deleted file mode 100644
index 74b50a4ef4..0000000000
--- a/lib/chef/knife/osc_user_create.rb
+++ /dev/null
@@ -1,97 +0,0 @@
-#
-# Author:: Steven Danna (<steve@chef.io>)
-# 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/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_hash(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_edit.rb b/lib/chef/knife/osc_user_edit.rb
deleted file mode 100644
index 89986c6f04..0000000000
--- a/lib/chef/knife/osc_user_edit.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-#
-# Author:: Steven Danna (<steve@chef.io>)
-# 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/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_hash(original_user)
- if original_user != edited_user
- user = Chef::User.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/osc_user_reregister.rb b/lib/chef/knife/osc_user_reregister.rb
deleted file mode 100644
index b513f31328..0000000000
--- a/lib/chef/knife/osc_user_reregister.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-#
-# Author:: Steven Danna (<steve@chef.io>)
-# 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/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/raw.rb b/lib/chef/knife/raw.rb
index 76b83d2212..5adb36ea70 100644
--- a/lib/chef/knife/raw.rb
+++ b/lib/chef/knife/raw.rb
@@ -1,48 +1,68 @@
-require "chef/knife"
-require "chef/http"
+#
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../knife"
class Chef
class Knife
class Raw < Chef::Knife
- banner "knife raw REQUEST_PATH"
+ banner "knife raw REQUEST_PATH (options)"
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_relative "../json_compat"
+ require_relative "../config"
+ require_relative "../http"
+ require_relative "../http/authenticator"
+ require_relative "../http/cookie_manager"
+ require_relative "../http/decompressor"
+ require_relative "../http/json_output"
end
option :method,
- :long => "--method METHOD",
- :short => "-m METHOD",
- :default => "GET",
- :description => "Request method (GET, POST, PUT or DELETE). Default: GET"
+ long: "--method METHOD",
+ short: "-m METHOD",
+ default: "GET",
+ description: "Request method (GET, POST, PUT or DELETE). Default: GET."
option :pretty,
- :long => "--[no-]pretty",
- :boolean => true,
- :default => true,
- :description => "Pretty-print JSON output. Default: true"
+ long: "--[no-]pretty",
+ boolean: true,
+ default: true,
+ description: "Pretty-print JSON output. Default: true."
option :input,
- :long => "--input FILE",
- :short => "-i FILE",
- :description => "Name of file to use for PUT or POST"
+ long: "--input FILE",
+ short: "-i FILE",
+ description: "Name of file to use for PUT or POST."
option :proxy_auth,
- :long => "--proxy-auth",
- :boolean => true,
- :default => false,
- :description => "Use webui proxy authentication. Client key must be the webui key."
+ long: "--proxy-auth",
+ boolean: true,
+ default: false,
+ description: "Use webui proxy authentication. Client key must be the webui key."
+ # We need a custom HTTP client class here because we don't want to even
+ # try to decode the body, in case we get back corrupted JSON or whatnot.
class RawInputServerAPI < Chef::HTTP
def initialize(options = {})
+ # If making a change here, also update Chef::ServerAPI.
options[:client_name] ||= Chef::Config[:node_name]
- options[:signing_key_filename] ||= Chef::Config[:client_key]
+ options[:raw_key] ||= Chef::Config[:client_key_contents]
+ options[:signing_key_filename] ||= Chef::Config[:client_key] unless options[:raw_key]
+ options[:ssh_agent_signing] ||= Chef::Config[:ssh_agent_signing]
super(Chef::Config[:chef_server_url], options)
end
use Chef::HTTP::JSONOutput
@@ -84,14 +104,14 @@ class Chef
result = Chef::JSONCompat.to_json_pretty(result)
end
else
- chef_rest = RawInputServerAPI.new(:raw_output => true)
+ chef_rest = RawInputServerAPI.new(raw_output: true)
result = chef_rest.request(method, name_args[0], headers, data)
end
output result
rescue Timeout::Error => e
ui.error "Server timeout"
exit 1
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => e
ui.error "Server responded with error #{e.response.code} \"#{e.response.message}\""
ui.error "Error Body: #{e.response.body}" if e.response.body && e.response.body != ""
exit 1
diff --git a/lib/chef/knife/recipe_list.rb b/lib/chef/knife/recipe_list.rb
index 8f76e494ad..39e040a2f4 100644
--- a/lib/chef/knife/recipe_list.rb
+++ b/lib/chef/knife/recipe_list.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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"
+require_relative "../knife"
class Chef::Knife::RecipeList < Chef::Knife
banner "knife recipe list [PATTERN]"
diff --git a/lib/chef/knife/rehash.rb b/lib/chef/knife/rehash.rb
index 79286368f8..69ee19229a 100644
--- a/lib/chef/knife/rehash.rb
+++ b/lib/chef/knife/rehash.rb
@@ -1,6 +1,6 @@
#
# Author:: Steven Danna <steve@chef.io>
-# Copyright:: Copyright 2015-2016, Chef Software, Inc
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,14 +16,17 @@
# limitations under the License.
#
-require "chef/knife"
-require "chef/knife/core/subcommand_loader"
+require_relative "../knife"
class Chef
class Knife
class Rehash < Chef::Knife
banner "knife rehash"
+ deps do
+ require_relative "core/subcommand_loader"
+ end
+
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."
@@ -31,7 +34,9 @@ class Chef
else
reload_plugins
end
- write_hash(generate_hash)
+
+ ui.msg "Knife subcommands are cached in #{Chef::Knife::SubcommandLoader.plugin_manifest_path}. Delete this file to disable the caching."
+ Chef::Knife::SubcommandLoader.write_hash(Chef::Knife::SubcommandLoader.generate_hash)
end
def reload_plugins
@@ -40,26 +45,6 @@ class Chef
# loaded plugins and `load_commands` shouldn't have an effect.
Chef::Knife.subcommand_loader.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 0726454da3..f57ac79619 100644
--- a/lib/chef/knife/role_bulk_delete.rb
+++ b/lib/chef/knife/role_bulk_delete.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class RoleBulkDelete < Knife
deps do
- require "chef/role"
- require "chef/json_compat"
+ require_relative "../role"
+ require_relative "../json_compat"
end
banner "knife role bulk delete REGEX (options)"
@@ -40,7 +40,8 @@ class Chef
matcher = /#{@name_args[0]}/
roles_to_delete = {}
all_roles.each do |name, role|
- next unless name =~ matcher
+ next unless name&.match?(matcher)
+
roles_to_delete[role.name] = role
end
diff --git a/lib/chef/knife/role_create.rb b/lib/chef/knife/role_create.rb
index a389d849f7..295445554d 100644
--- a/lib/chef/knife/role_create.rb
+++ b/lib/chef/knife/role_create.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,23 +16,23 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class RoleCreate < Knife
deps do
- require "chef/role"
- require "chef/json_compat"
+ require_relative "../role"
+ require_relative "../json_compat"
end
banner "knife role create ROLE (options)"
option :description,
- :short => "-d DESC",
- :long => "--description DESC",
- :description => "The role description"
+ short: "-d DESC",
+ long: "--description DESC",
+ description: "The role description."
def run
@role_name = @name_args[0]
diff --git a/lib/chef/knife/role_delete.rb b/lib/chef/knife/role_delete.rb
index 5c10a05d85..c46e265c5e 100644
--- a/lib/chef/knife/role_delete.rb
+++ b/lib/chef/knife/role_delete.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class RoleDelete < Knife
deps do
- require "chef/role"
- require "chef/json_compat"
+ require_relative "../role"
+ require_relative "../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 d3697849ad..1925336646 100644
--- a/lib/chef/knife/role_edit.rb
+++ b/lib/chef/knife/role_edit.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class RoleEdit < Knife
deps do
- require "chef/role"
- require "chef/json_compat"
+ require_relative "../role"
+ require_relative "../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 61aec506a9..b5753b46fc 100644
--- a/lib/chef/knife/role_env_run_list_add.rb
+++ b/lib/chef/knife/role_env_run_list_add.rb
@@ -1,6 +1,7 @@
+#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: William Albenzi (<walbenzi@gmail.com>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,23 +17,23 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class RoleEnvRunListAdd < Knife
deps do
- require "chef/role"
- require "chef/json_compat"
+ require_relative "../role"
+ require_relative "../json_compat"
end
- banner "knife role env_run_list add [ROLE] [ENVIRONMENT] [ENTRY[,ENTRY]] (options)"
+ banner "knife role env_run_list add [ROLE] [ENVIRONMENT] [ENTRY [ENTRY]] (options)"
option :after,
- :short => "-a ITEM",
- :long => "--after ITEM",
- :description => "Place the ENTRY in the run list after ITEM"
+ short: "-a ITEM",
+ long: "--after ITEM",
+ description: "Place the ENTRY in the run list after ITEM."
def add_to_env_run_list(role, environment, entries, after = nil)
if after
@@ -67,12 +68,12 @@ 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 }
+ entries = @name_args[2..].map do |entry|
+ entry.split(",").map(&: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(&: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 d9dc96c87d..dda523e809 100644
--- a/lib/chef/knife/role_env_run_list_clear.rb
+++ b/lib/chef/knife/role_env_run_list_clear.rb
@@ -17,18 +17,18 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class RoleEnvRunListClear < Knife
deps do
- require "chef/role"
- require "chef/json_compat"
+ require_relative "../role"
+ require_relative "../json_compat"
end
- banner "knife role env_run_list clear [ROLE] [ENVIRONMENT]"
+ banner "knife role env_run_list clear [ROLE] [ENVIRONMENT] (options)"
def clear_env_run_list(role, environment)
nlist = []
role.env_run_lists_add(environment => nlist)
diff --git a/lib/chef/knife/role_env_run_list_remove.rb b/lib/chef/knife/role_env_run_list_remove.rb
index 576e32e2a9..57363610ce 100644
--- a/lib/chef/knife/role_env_run_list_remove.rb
+++ b/lib/chef/knife/role_env_run_list_remove.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,26 +16,26 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class RoleEnvRunListRemove < Knife
deps do
- require "chef/role"
- require "chef/json_compat"
+ require_relative "../role"
+ require_relative "../json_compat"
end
- banner "knife role env_run_list remove [ROLE] [ENVIRONMENT] [ENTRIES]"
+ banner "knife role env_run_list remove [ROLE] [ENVIRONMENT] [ENTRIES] (options)"
def remove_from_env_run_list(role, environment, item_to_remove)
nlist = []
role.run_list_for(environment).each do |entry|
nlist << entry unless entry == item_to_remove
- #unless entry == @name_args[2]
+ # unless entry == @name_args[2]
# nlist << entry
- #end
+ # end
end
role.env_run_lists_add(environment => nlist)
end
diff --git a/lib/chef/knife/role_env_run_list_replace.rb b/lib/chef/knife/role_env_run_list_replace.rb
index e84e351c1e..e76680661e 100644
--- a/lib/chef/knife/role_env_run_list_replace.rb
+++ b/lib/chef/knife/role_env_run_list_replace.rb
@@ -1,6 +1,7 @@
+#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: William Albenzi (<walbenzi@gmail.com>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,18 +17,18 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class RoleEnvRunListReplace < Knife
deps do
- require "chef/role"
- require "chef/json_compat"
+ require_relative "../role"
+ require_relative "../json_compat"
end
- banner "knife role env_run_list replace [ROLE] [ENVIRONMENT] [OLD_ENTRY] [NEW_ENTRY] "
+ banner "knife role env_run_list replace [ROLE] [ENVIRONMENT] [OLD_ENTRY] [NEW_ENTRY] (options)"
def replace_in_env_run_list(role, environment, old_entry, new_entry)
nlist = []
diff --git a/lib/chef/knife/role_env_run_list_set.rb b/lib/chef/knife/role_env_run_list_set.rb
index f53616e151..0f1ce62a5d 100644
--- a/lib/chef/knife/role_env_run_list_set.rb
+++ b/lib/chef/knife/role_env_run_list_set.rb
@@ -17,18 +17,18 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class RoleEnvRunListSet < Knife
deps do
- require "chef/role"
- require "chef/json_compat"
+ require_relative "../role"
+ require_relative "../json_compat"
end
- banner "knife role env_run_list set [ROLE] [ENVIRONMENT] [ENTRIES]"
+ banner "knife role env_run_list set [ROLE] [ENVIRONMENT] [ENTRIES] (options)"
# Clears out any existing env_run_list_items and sets them to the
# specified entries
@@ -51,12 +51,12 @@ class Chef
exit 1
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 }
+ entries = @name_args[2..].map do |entry|
+ entry.split(",").map(&: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(&: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 bf21a38fd7..16e38eeb63 100644
--- a/lib/chef/knife/role_from_file.rb
+++ b/lib/chef/knife/role_from_file.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,16 +16,16 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class RoleFromFile < Knife
deps do
- require "chef/role"
- require "chef/knife/core/object_loader"
- require "chef/json_compat"
+ require_relative "../role"
+ require_relative "core/object_loader"
+ require_relative "../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 1247478ef5..d6aad053c1 100644
--- a/lib/chef/knife/role_list.rb
+++ b/lib/chef/knife/role_list.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,23 +16,23 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class RoleList < Knife
deps do
- require "chef/node"
- require "chef/json_compat"
+ require_relative "../node"
+ require_relative "../json_compat"
end
banner "knife role list (options)"
option :with_uri,
- :short => "-w",
- :long => "--with-uri",
- :description => "Show corresponding URIs"
+ short: "-w",
+ long: "--with-uri",
+ description: "Show corresponding URIs."
def run
output(format_list_for_display(Chef::Role.list))
diff --git a/lib/chef/knife/role_run_list_add.rb b/lib/chef/knife/role_run_list_add.rb
index 6aa92d37ba..76633ff5f6 100644
--- a/lib/chef/knife/role_run_list_add.rb
+++ b/lib/chef/knife/role_run_list_add.rb
@@ -1,6 +1,7 @@
+#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: William Albenzi (<walbenzi@gmail.com>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,23 +17,23 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class RoleRunListAdd < Knife
deps do
- require "chef/role"
- require "chef/json_compat"
+ require_relative "../role"
+ require_relative "../json_compat"
end
- banner "knife role run_list add [ROLE] [ENTRY[,ENTRY]] (options)"
+ banner "knife role run_list add [ROLE] [ENTRY [ENTRY]] (options)"
option :after,
- :short => "-a ITEM",
- :long => "--after ITEM",
- :description => "Place the ENTRY in the run list after ITEM"
+ short: "-a ITEM",
+ long: "--after ITEM",
+ description: "Place the ENTRY in the run list after ITEM."
def add_to_env_run_list(role, environment, entries, after = nil)
if after
@@ -67,12 +68,12 @@ 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 }
+ entries = @name_args[1..].map do |entry|
+ entry.split(",").map(&: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(&: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 81678d39ef..b7106233f0 100644
--- a/lib/chef/knife/role_run_list_clear.rb
+++ b/lib/chef/knife/role_run_list_clear.rb
@@ -17,18 +17,18 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class RoleRunListClear < Knife
deps do
- require "chef/role"
- require "chef/json_compat"
+ require_relative "../role"
+ require_relative "../json_compat"
end
- banner "knife role run_list clear [ROLE]"
+ banner "knife role run_list clear [ROLE] (options)"
def clear_env_run_list(role, environment)
nlist = []
role.env_run_lists_add(environment => nlist)
diff --git a/lib/chef/knife/role_run_list_remove.rb b/lib/chef/knife/role_run_list_remove.rb
index 0dacfee051..884f3bc28d 100644
--- a/lib/chef/knife/role_run_list_remove.rb
+++ b/lib/chef/knife/role_run_list_remove.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,26 +16,25 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class RoleRunListRemove < Knife
deps do
- require "chef/role"
- require "chef/json_compat"
+ require_relative "../role"
end
- banner "knife role run_list remove [ROLE] [ENTRY]"
+ banner "knife role run_list remove [ROLE] [ENTRY] (options)"
def remove_from_env_run_list(role, environment, item_to_remove)
nlist = []
role.run_list_for(environment).each do |entry|
nlist << entry unless entry == item_to_remove
- #unless entry == @name_args[2]
+ # unless entry == @name_args[2]
# nlist << entry
- #end
+ # end
end
role.env_run_lists_add(environment => nlist)
end
diff --git a/lib/chef/knife/role_run_list_replace.rb b/lib/chef/knife/role_run_list_replace.rb
index 3e7bc2d5ec..16f789fbef 100644
--- a/lib/chef/knife/role_run_list_replace.rb
+++ b/lib/chef/knife/role_run_list_replace.rb
@@ -1,6 +1,7 @@
+#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: William Albenzi (<walbenzi@gmail.com>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,18 +17,18 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class RoleRunListReplace < Knife
deps do
- require "chef/role"
- require "chef/json_compat"
+ require_relative "../role"
+ require_relative "../json_compat"
end
- banner "knife role run_list replace [ROLE] [OLD_ENTRY] [NEW_ENTRY] "
+ banner "knife role run_list replace [ROLE] [OLD_ENTRY] [NEW_ENTRY] (options)"
def replace_in_env_run_list(role, environment, old_entry, new_entry)
nlist = []
diff --git a/lib/chef/knife/role_run_list_set.rb b/lib/chef/knife/role_run_list_set.rb
index 644af1c381..ad1a5e2923 100644
--- a/lib/chef/knife/role_run_list_set.rb
+++ b/lib/chef/knife/role_run_list_set.rb
@@ -17,18 +17,17 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class RoleRunListSet < Knife
deps do
- require "chef/role"
- require "chef/json_compat"
+ require_relative "../role"
end
- banner "knife role run_list set [ROLE] [ENTRIES]"
+ banner "knife role run_list set [ROLE] [ENTRIES] (options)"
# Clears out any existing env_run_list_items and sets them to the
# specified entries
@@ -51,12 +50,12 @@ class Chef
exit 1
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 }
+ entries = @name_args[1..].map do |entry|
+ entry.split(",").map(&: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(&: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 99684768bb..ee90352e50 100644
--- a/lib/chef/knife/role_show.rb
+++ b/lib/chef/knife/role_show.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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"
+require_relative "../knife"
class Chef
class Knife
@@ -25,8 +25,7 @@ class Chef
include Knife::Core::MultiAttributeReturnOption
deps do
- require "chef/node"
- require "chef/json_compat"
+ require_relative "../role"
end
banner "knife role show ROLE (options)"
@@ -36,7 +35,7 @@ class Chef
if @role_name.nil?
show_usage
- ui.fatal("You must specify a role name")
+ ui.fatal("You must specify a role name.")
exit 1
end
diff --git a/lib/chef/knife/search.rb b/lib/chef/knife/search.rb
index d102c1e955..620cfb971d 100644
--- a/lib/chef/knife/search.rb
+++ b/lib/chef/knife/search.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/knife"
-require "chef/knife/core/node_presenter"
-require "addressable/uri"
+require_relative "../knife"
+require_relative "core/node_presenter"
+require_relative "core/formatting_options"
class Chef
class Knife
@@ -27,84 +27,80 @@ class Chef
include Knife::Core::MultiAttributeReturnOption
deps do
- require "chef/node"
- require "chef/environment"
- require "chef/api_client"
- require "chef/search/query"
+ require_relative "../node"
+ require_relative "../environment"
+ require_relative "../api_client"
+ require_relative "../search/query"
end
- include Knife::Core::NodeFormattingOptions
+ include Knife::Core::FormattingOptions
banner "knife search INDEX QUERY (options)"
- option :sort,
- :short => "-o SORT",
- :long => "--sort SORT",
- :description => "The order to sort the results in",
- :default => nil
-
option :start,
- :short => "-b ROW",
- :long => "--start ROW",
- :description => "The row to start returning results at",
- :default => 0,
- :proc => lambda { |i| i.to_i }
+ short: "-b ROW",
+ long: "--start ROW",
+ description: "The row to start returning results at.",
+ default: 0,
+ proc: lambda { |i| i.to_i }
option :rows,
- :short => "-R INT",
- :long => "--rows INT",
- :description => "The number of rows to return",
- :default => nil,
- :proc => lambda { |i| i.to_i }
+ short: "-R INT",
+ long: "--rows INT",
+ description: "The number of rows to return.",
+ default: nil,
+ proc: lambda { |i| i.to_i }
option :run_list,
- :short => "-r",
- :long => "--run-list",
- :description => "Show only the run list"
+ short: "-r",
+ long: "--run-list",
+ description: "Show only the run list."
option :id_only,
- :short => "-i",
- :long => "--id-only",
- :description => "Show only the ID of matching objects"
+ short: "-i",
+ long: "--id-only",
+ description: "Show only the ID of matching objects."
option :query,
- :short => "-q QUERY",
- :long => "--query QUERY",
- :description => "The search query; useful to protect queries starting with -"
+ short: "-q QUERY",
+ long: "--query QUERY",
+ description: "The search query; useful to protect queries starting with -."
option :filter_result,
- :short => "-f FILTER",
- :long => "--filter-result FILTER",
- :description => "Only return specific attributes of the matching objects; for example: \"ServerName=name, Kernel=kernel.version\""
+ short: "-f FILTER",
+ long: "--filter-result FILTER",
+ description: "Only return specific attributes of the matching objects; for example: \"ServerName=name, Kernel=kernel.version\"."
def run
read_cli_args
- fuzzify_query
if @type == "node"
ui.use_presenter Knife::Core::NodePresenter
end
q = Chef::Search::Query.new
- escaped_query = Addressable::URI.encode_component(@query, Addressable::URI::CharacterClasses::QUERY)
result_items = []
result_count = 0
- search_args = Hash.new
- search_args[:sort] = config[:sort] if config[:sort]
+ search_args = {}
+ search_args[:fuzz] = true
search_args[:start] = config[:start] if config[:start]
search_args[:rows] = config[:rows] if config[:rows]
if config[:filter_result]
search_args[:filter_result] = create_result_filter(config[:filter_result])
elsif (not ui.config[:attribute].nil?) && (not ui.config[:attribute].empty?)
search_args[:filter_result] = create_result_filter_from_attributes(ui.config[:attribute])
+ elsif config[:id_only]
+ search_args[:filter_result] = create_result_filter_from_attributes([])
end
begin
- q.search(@type, escaped_query, search_args) do |item|
- formatted_item = Hash.new
- if item.is_a?(Hash)
+ q.search(@type, @query, search_args) do |item|
+ formatted_item = {}
+ if config[:id_only]
+ formatted_item = format_for_display({ "id" => item["__display_name"] })
+ elsif item.is_a?(Hash)
# doing a little magic here to set the correct name
formatted_item[item["__display_name"]] = item.reject { |k| k == "__display_name" }
else
@@ -113,14 +109,14 @@ class Chef
result_items << formatted_item
result_count += 1
end
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => e
msg = Chef::JSONCompat.from_json(e.response.body)["error"].first
ui.error("knife search failed: #{msg}")
- exit 1
+ exit 99
end
if ui.interchange?
- output({ :results => result_count, :rows => result_items })
+ output({ results: result_count, rows: result_items })
else
ui.log "#{result_count} items found"
ui.log("\n")
@@ -131,6 +127,9 @@ class Chef
end
end
end
+
+ # return a "failure" code to the shell so that knife search can be used in pipes similar to grep
+ exit 1 if result_count == 0
end
def read_cli_args
@@ -158,12 +157,6 @@ class Chef
end
end
- def fuzzify_query
- if @query !~ /:/
- @query = "tags:*#{@query}* OR roles:*#{@query}* OR fqdn:*#{@query}* OR addresses:*#{@query}* OR policy_name:*#{@query}* OR policy_group:*#{@query}*"
- end
- end
-
# This method turns a set of key value pairs in a string into the appropriate data structure that the
# chef-server search api is expecting.
# expected input is in the form of:
@@ -176,24 +169,24 @@ class Chef
# and the path is an array with the path elements as strings (in order)
# See lib/chef/search/query.rb for more examples of this.
def create_result_filter(filter_string)
- final_filter = Hash.new
+ final_filter = {}
filter_string.delete!(" ")
filters = filter_string.split(",")
filters.each do |f|
return_id, attr_path = f.split("=")
final_filter[return_id.to_sym] = attr_path.split(".")
end
- return final_filter
+ final_filter
end
def create_result_filter_from_attributes(filter_array)
- final_filter = Hash.new
+ final_filter = {}
filter_array.each do |f|
final_filter[f] = f.split(".")
end
# adding magic filter so we can actually pull the name as before
final_filter["__display_name"] = [ "name" ]
- return final_filter
+ final_filter
end
end
diff --git a/lib/chef/knife/serve.rb b/lib/chef/knife/serve.rb
index 95996e6d1e..d79e05aa85 100644
--- a/lib/chef/knife/serve.rb
+++ b/lib/chef/knife/serve.rb
@@ -1,5 +1,22 @@
-require "chef/knife"
-require "chef/local_mode"
+#
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../knife"
+require_relative "../local_mode"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Knife
@@ -8,16 +25,16 @@ class Chef
banner "knife serve (options)"
option :repo_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"
+ 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 #{ChefUtils::Dist::Infra::PRODUCT} 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 #{ChefUtils::Dist::Zero::PRODUCT} listens. Default is 127.0.0.1."
def configure_chef
super
diff --git a/lib/chef/knife/show.rb b/lib/chef/knife/show.rb
index 4c1c882815..0e5ab9d0fe 100644
--- a/lib/chef/knife/show.rb
+++ b/lib/chef/knife/show.rb
@@ -1,21 +1,37 @@
-require "chef/chef_fs/knife"
+#
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../chef_fs/knife"
class Chef
class Knife
class Show < Chef::ChefFS::Knife
- banner "knife show [PATTERN1 ... PATTERNn]"
+ banner "knife show [PATTERN1 ... PATTERNn] (options)"
category "path-based"
deps do
- require "chef/chef_fs/file_system"
- require "chef/chef_fs/file_system/exceptions"
+ require_relative "../chef_fs/file_system"
+ require_relative "../chef_fs/file_system/exceptions"
end
option :local,
- :long => "--local",
- :boolean => true,
- :description => "Show local files instead of remote"
+ long: "--local",
+ boolean: true,
+ description: "Show local files instead of remote."
def run
# Get the matches (recursively)
diff --git a/lib/chef/knife/ssh.rb b/lib/chef/knife/ssh.rb
index 6f266b2719..8681fdfd02 100644
--- a/lib/chef/knife/ssh.rb
+++ b/lib/chef/knife/ssh.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,139 +16,143 @@
# limitations under the License.
#
-require "chef/mixin/shell_out"
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class Ssh < Knife
deps do
- require "net/ssh"
+ require_relative "../mixin/shell_out"
+ require "net/ssh" unless defined?(Net::SSH)
require "net/ssh/multi"
- require "chef/monkey_patches/net-ssh-multi"
require "readline"
- require "chef/exceptions"
- require "chef/search/query"
- require "chef/util/path_helper"
- require "mixlib/shellout"
- end
+ require_relative "../exceptions"
+ require_relative "../search/query"
+ require_relative "../util/path_helper"
- include Chef::Mixin::ShellOut
+ include Chef::Mixin::ShellOut
+ end
attr_writer :password
banner "knife ssh QUERY COMMAND (options)"
option :concurrency,
- :short => "-C NUM",
- :long => "--concurrency NUM",
- :description => "The number of concurrent connections",
- :default => nil,
- :proc => lambda { |o| o.to_i }
-
- option :attribute,
- :short => "-a ATTR",
- :long => "--attribute ATTR",
- :description => "The attribute to use for opening the connection - default depends on the context",
- :proc => Proc.new { |key| Chef::Config[:knife][:ssh_attribute] = key.strip }
+ short: "-C NUM",
+ long: "--concurrency NUM",
+ description: "The number of concurrent connections.",
+ default: nil,
+ proc: lambda { |o| o.to_i }
+
+ option :ssh_attribute,
+ short: "-a ATTR",
+ long: "--attribute ATTR",
+ description: "The attribute to use for opening the connection - default depends on the context."
option :manual,
- :short => "-m",
- :long => "--manual-list",
- :boolean => true,
- :description => "QUERY is a space separated list of servers",
- :default => false
+ short: "-m",
+ long: "--manual-list",
+ boolean: true,
+ description: "QUERY is a space separated list of servers.",
+ default: false
+
+ option :prefix_attribute,
+ long: "--prefix-attribute ATTR",
+ description: "The attribute to use for prefixing the output - default depends on the context."
option :ssh_user,
- :short => "-x USERNAME",
- :long => "--ssh-user USERNAME",
- :description => "The ssh username"
-
- option :ssh_password_ng,
- :short => "-P [PASSWORD]",
- :long => "--ssh-password [PASSWORD]",
- :description => "The ssh password - will prompt if flag is specified but no password is given",
+ short: "-x USERNAME",
+ long: "--ssh-user USERNAME",
+ description: "The ssh username."
+
+ option :ssh_password,
+ short: "-P [PASSWORD]",
+ long: "--ssh-password [PASSWORD]",
+ 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 value
- :default => false
+ default: false
option :ssh_port,
- :short => "-p PORT",
- :long => "--ssh-port PORT",
- :description => "The ssh port",
- :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key.strip }
+ short: "-p PORT",
+ long: "--ssh-port PORT",
+ description: "The ssh port.",
+ proc: Proc.new { |key| key.strip }
option :ssh_timeout,
- :short => "-t SECONDS",
- :long => "--ssh-timeout SECONDS",
- :description => "The ssh connection timeout",
- :proc => Proc.new { |key| Chef::Config[:knife][:ssh_timeout] = key.strip.to_i },
- :default => 120
+ short: "-t SECONDS",
+ long: "--ssh-timeout SECONDS",
+ description: "The ssh connection timeout.",
+ proc: Proc.new { |key| key.strip.to_i },
+ default: 120
option :ssh_gateway,
- :short => "-G GATEWAY",
- :long => "--ssh-gateway GATEWAY",
- :description => "The ssh gateway",
- :proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway] = key.strip }
+ short: "-G GATEWAY",
+ long: "--ssh-gateway GATEWAY",
+ description: "The ssh gateway.",
+ proc: Proc.new { |key| key.strip }
- option :forward_agent,
- :short => "-A",
- :long => "--forward-agent",
- :description => "Enable SSH agent forwarding",
- :boolean => true
+ option :ssh_gateway_identity,
+ long: "--ssh-gateway-identity SSH_GATEWAY_IDENTITY",
+ description: "The SSH identity file used for gateway authentication."
- option :identity_file,
- :long => "--identity-file IDENTITY_FILE",
- :description => "The SSH identity file used for authentication. [DEPRECATED] Use --ssh-identity-file instead."
+ option :forward_agent,
+ short: "-A",
+ long: "--forward-agent",
+ description: "Enable SSH agent forwarding.",
+ boolean: true
option :ssh_identity_file,
- :short => "-i IDENTITY_FILE",
- :long => "--ssh-identity-file IDENTITY_FILE",
- :description => "The SSH identity file used for authentication"
+ short: "-i IDENTITY_FILE",
+ long: "--ssh-identity-file IDENTITY_FILE",
+ description: "The SSH identity file used for authentication."
option :host_key_verify,
- :long => "--[no-]host-key-verify",
- :description => "Verify host key, enabled by default.",
- :boolean => true,
- :default => true
+ long: "--[no-]host-key-verify",
+ description: "Verify host key, enabled by default.",
+ boolean: true,
+ default: true
option :on_error,
- :short => "-e",
- :long => "--exit-on-error",
- :description => "Immediately exit if an error is encountered",
- :boolean => true,
- :proc => Proc.new { :raise }
+ short: "-e",
+ long: "--exit-on-error",
+ description: "Immediately exit if an error is encountered.",
+ boolean: true,
+ default: false
+
+ option :duplicated_fqdns,
+ long: "--duplicated-fqdns",
+ description: "Behavior if FQDNs are duplicated, ignored by default.",
+ proc: Proc.new { |key| key.strip.to_sym },
+ default: :ignore
option :tmux_split,
- :long => "--tmux-split",
- :description => "Split tmux window.",
- :boolean => true,
- :default => false
+ long: "--tmux-split",
+ description: "Split tmux window.",
+ boolean: true,
+ default: false
def session
- config[:on_error] ||= :skip
ssh_error_handler = Proc.new do |server|
- case config[:on_error]
- when :skip
+ if config[:on_error]
+ # Net::SSH::Multi magic to force exception to be re-raised.
+ throw :go, :raise
+ else
ui.warn "Failed to connect to #{server.host} -- #{$!.class.name}: #{$!.message}"
$!.backtrace.each { |l| Chef::Log.debug(l) }
- when :raise
- #Net::SSH::Multi magic to force exception to be re-raised.
- throw :go, :raise
end
end
- @session ||= Net::SSH::Multi.start(:concurrent_connections => config[:concurrency], :on_error => ssh_error_handler)
+ @session ||= Net::SSH::Multi.start(concurrent_connections: config[:concurrency], on_error: ssh_error_handler)
end
def configure_gateway
- config[:ssh_gateway] ||= Chef::Config[:knife][:ssh_gateway]
if config[:ssh_gateway]
gw_host, gw_user = config[:ssh_gateway].split("@").reverse
gw_host, gw_port = gw_host.split(":")
- gw_opts = session_options(gw_host, gw_port, gw_user)
+ gw_opts = session_options(gw_host, gw_port, gw_user, gateway: true)
user = gw_opts.delete(:user)
begin
@@ -166,59 +170,92 @@ class Chef
def configure_session
list = config[:manual] ? @name_args[0].split(" ") : search_nodes
if list.length == 0
- if @action_nodes.length == 0
+ if @search_count == 0
ui.fatal("No nodes returned from search")
else
- ui.fatal("#{@action_nodes.length} #{@action_nodes.length > 1 ? "nodes" : "node"} found, " +
+ ui.fatal("#{@search_count} #{@search_count > 1 ? "nodes" : "node"} found, " +
"but does not have the required attribute to establish the connection. " +
"Try setting another attribute to open the connection using --attribute.")
end
exit 10
end
+ if %i{warn fatal}.include?(config[:duplicated_fqdns])
+ fqdns = list.map { |v| v[0] }
+ if fqdns.count != fqdns.uniq.count
+ duplicated_fqdns = fqdns.uniq
+ ui.send(config[:duplicated_fqdns],
+ "SSH #{duplicated_fqdns.count > 1 ? "nodes are" : "node is"} " +
+ "duplicated: #{duplicated_fqdns.join(",")}")
+ exit 10 if config[:duplicated_fqdns] == :fatal
+ end
+ end
session_from_list(list)
end
- def get_ssh_attribute(node)
+ def get_prefix_attribute(item)
+ # Order of precedence for prefix
+ # 1) config value (cli or knife config)
+ # 2) nil
+ msg = "Using node attribute '%s' as the prefix: %s"
+ if item["prefix"]
+ Chef::Log.debug(sprintf(msg, config[:prefix_attribute], item["prefix"]))
+ item["prefix"]
+ else
+ nil
+ end
+ end
+
+ def get_ssh_attribute(item)
# 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"
+ # 1) config value (cli or knife config)
+ # 2) cloud attribute
+ # 3) fqdn
+ msg = "Using node attribute '%s' as the ssh target: %s"
+ if item["target"]
+ Chef::Log.debug(sprintf(msg, config[:ssh_attribute], item["target"]))
+ item["target"]
+ elsif !item.dig("cloud", "public_hostname").to_s.empty?
+ Chef::Log.debug(sprintf(msg, "cloud.public_hostname", item["cloud"]["public_hostname"]))
+ item["cloud"]["public_hostname"]
else
- # falling back to default of fqdn
- Chef::Log.debug("Using node attribute 'fqdn' as the ssh target")
- attribute = "fqdn"
+ Chef::Log.debug(sprintf(msg, "fqdn", item["fqdn"]))
+ item["fqdn"]
end
- attribute
end
def search_nodes
- list = Array.new
+ list = []
query = Chef::Search::Query.new
- @action_nodes = query.search(:node, @name_args[0])[0]
- @action_nodes.each do |item|
+ required_attributes = { fqdn: ["fqdn"], cloud: ["cloud"] }
+
+ separator = ui.presenter.attribute_field_separator
+
+ if config[:prefix_attribute]
+ required_attributes[:prefix] = config[:prefix_attribute].split(separator)
+ end
+
+ if config[:ssh_attribute]
+ required_attributes[:target] = config[:ssh_attribute].split(separator)
+ end
+
+ @search_count = 0
+ query.search(:node, @name_args[0], filter_result: required_attributes, fuzz: true) do |item|
+ @search_count += 1
# we should skip the loop to next iteration if the item
# returned by the search is nil
next if item.nil?
+
# next if we couldn't find the specified attribute in the
# returned node object
- host = extract_nested_value(item, get_ssh_attribute(item))
+ host = get_ssh_attribute(item)
next if host.nil?
- ssh_port = item[:cloud].nil? ? nil : item[:cloud][:public_ssh_port]
- srv = [host, ssh_port]
+
+ prefix = get_prefix_attribute(item)
+ ssh_port = item.dig("cloud", "public_ssh_port")
+ srv = [host, ssh_port, prefix]
list.push(srv)
end
+
list
end
@@ -230,15 +267,18 @@ class Chef
# @param host [String] Hostname for this session.
# @param port [String] SSH port for this session.
# @param user [String] Optional username for this session.
+ # @param gateway [Boolean] Flag: host or gateway key
# @return [Hash<Symbol, Object>]
- def session_options(host, port, user = nil)
- ssh_config = Net::SSH.configuration_for(host)
+ def session_options(host, port, user = nil, gateway: false)
+ ssh_config = Net::SSH.configuration_for(host, true)
{}.tap do |opts|
- # Chef::Config[:knife][:ssh_user] is parsed in #configure_user and written to config[:ssh_user]
opts[:user] = user || config[:ssh_user] || ssh_config[:user]
- if config[:ssh_identity_file]
+ if !gateway && config[:ssh_identity_file]
opts[:keys] = File.expand_path(config[:ssh_identity_file])
opts[:keys_only] = true
+ elsif gateway && config[:ssh_gateway_identity]
+ opts[:keys] = File.expand_path(config[:ssh_gateway_identity])
+ opts[:keys_only] = true
elsif config[:ssh_password]
opts[:password] = config[:ssh_password]
end
@@ -247,38 +287,47 @@ class Chef
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]
- opts[:paranoid] = false
+ opts[:logger] = Chef::Log.with_child(subsystem: "net/ssh") if Chef::Log.level == :trace
+ unless config[:host_key_verify]
+ opts[:verify_host_key] = :never
opts[:user_known_hosts_file] = "/dev/null"
end
+ if ssh_config[:keepalive]
+ opts[:keepalive] = true
+ opts[:keepalive_interval] = ssh_config[:keepalive_interval]
+ end
+ # maintain support for legacy key types / ciphers / key exchange algorithms.
+ # most importantly this adds back support for DSS host keys
+ # See https://github.com/net-ssh/net-ssh/pull/709
+ opts[:append_all_supported_algorithms] = true
end
end
def session_from_list(list)
list.each do |item|
- host, ssh_port = item
+ host, ssh_port, prefix = item
+ prefix = host unless prefix
Chef::Log.debug("Adding #{host}")
- session_opts = session_options(host, ssh_port)
+ session_opts = session_options(host, ssh_port, gateway: false)
# 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]
# Handle connection timeout
- session_opts[:timeout] = Chef::Config[:knife][:ssh_timeout] if Chef::Config[:knife][:ssh_timeout]
session_opts[:timeout] = config[:ssh_timeout] if config[:ssh_timeout]
+ # Handle session prefix
+ session_opts[:properties] = { prefix: prefix }
# 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
+ @longest = prefix.length if prefix.length > @longest
end
session
end
def fixup_sudo(command)
- command.sub(/^sudo/, 'sudo -p \'knife sudo password: \'')
+ command.sub(/^sudo/, "sudo -p 'knife sudo password: '")
end
def print_data(host, data)
@@ -309,19 +358,41 @@ class Chef
subsession ||= session
command = fixup_sudo(command)
command.force_encoding("binary") if command.respond_to?(:force_encoding)
- subsession.open_channel do |ch|
- ch.request_pty
- ch.exec command do |ch, success|
- raise ArgumentError, "Cannot execute #{command}" unless success
- ch.on_data do |ichannel, data|
- print_data(ichannel[:host], data)
- if data =~ /^knife sudo password: /
- print_data(ichannel[:host], "\n")
- ichannel.send_data("#{get_password}\n")
+ begin
+ open_session(subsession, command)
+ rescue => e
+ open_session(subsession, command, true)
+ end
+ end
+
+ def open_session(subsession, command, pty = false)
+ stderr = ""
+ exit_status = 0
+ subsession.open_channel do |chan|
+ if config[:on_error] && exit_status != 0
+ chan.close
+ else
+ chan.request_pty if pty
+ chan.exec command do |ch, success|
+ raise ArgumentError, "Cannot execute #{command}" unless success
+
+ ch.on_data do |ichannel, data|
+ print_data(ichannel.connection[:prefix], data)
+ if /^knife sudo password: /.match?(data)
+ print_data(ichannel.connection[:prefix], "\n")
+ ichannel.send_data("#{get_password}\n")
+ end
+ end
+
+ ch.on_extended_data do |_, _type, data|
+ raise ArgumentError if data.eql?("sudo: no tty present and no askpass program specified\n")
+
+ stderr += data
+ end
+
+ ch.on_request "exit-status" do |ichannel, data|
+ exit_status = [exit_status, data.read_long].max
end
- end
- ch.on_request "exit-status" do |ichannel, data|
- exit_status = [exit_status, data.read_long].max
end
end
end
@@ -334,7 +405,7 @@ class Chef
end
def prompt_for_password(prompt = "Enter your password: ")
- ui.ask(prompt) { |q| q.echo = false }
+ ui.ask(prompt, echo: false)
end
# Present the prompt and read a single line from the console. It also
@@ -343,7 +414,7 @@ class Chef
# line is input.
def read_line
loop do
- command = reader.readline("#{ui.color('knife-ssh>', :bold)} ", true)
+ command = reader.readline("#{ui.color("knife-ssh>", :bold)} ", true)
if command.nil?
command = "exit"
@@ -379,7 +450,7 @@ class Chef
break
when /^on (.+?); (.+)$/
raw_list = $1.split(" ")
- server_list = Array.new
+ server_list = []
session.servers.each do |session_server|
server_list << session_server if raw_list.include?(session_server.host)
end
@@ -420,7 +491,7 @@ class Chef
new_window_cmds = lambda do
if session.servers_for.size > 1
- [""] + session.servers_for[1..-1].map do |server|
+ [""] + session.servers_for[1..].map do |server|
if config[:tmux_split]
"split-window #{ssh_dest.call(server)}; tmux select-layout tiled"
else
@@ -432,7 +503,7 @@ class Chef
end.join(" \\; ")
end
- tmux_name = "'knife ssh #{@name_args[0].tr(':', '=')}'"
+ tmux_name = "'knife ssh #{@name_args[0].tr(":.", "=-")}'"
begin
server = session.servers_for.first
cmd = ["tmux new-session -d -s #{tmux_name}",
@@ -446,37 +517,37 @@ class Chef
def macterm
begin
- require "appscript"
+ require "appscript" unless defined?(Appscript)
rescue LoadError
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)
+ Appscript.app("System Events").application_processes["Terminal.app"].keystroke("n", using: :command_down)
term = Appscript.app("Terminal")
window = term.windows.first.get
(session.servers_for.size - 1).times do |i|
window.activate
- Appscript.app("System Events").application_processes["Terminal.app"].keystroke("t", :using => :command_down)
+ Appscript.app("System Events").application_processes["Terminal.app"].keystroke("t", using: :command_down)
end
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 cssh
cssh_cmd = nil
%w{csshX cssh}.each do |cmd|
- begin
- # Unix and Mac only
- cssh_cmd = shell_out!("which #{cmd}").stdout.strip
- break
- rescue Mixlib::ShellOut::ShellCommandFailed
- end
+
+ # Unix and Mac only
+ cssh_cmd = shell_out!("which #{cmd}").stdout.strip
+ break
+ rescue Mixlib::ShellOut::ShellCommandFailed
+
end
raise Chef::Exceptions::Exec, "no command found for cssh" unless cssh_cmd
@@ -493,7 +564,8 @@ class Chef
end
def get_stripped_unfrozen_value(value)
- return nil if value.nil?
+ return nil unless value
+
value.strip
end
@@ -502,46 +574,43 @@ class Chef
Chef::Config[:knife][:ssh_user])
end
- # This is a bit overly complicated because of the way we want knife ssh to work with -P causing a password prompt for
- # the user, but we have to be conscious that this code gets included in knife bootstrap and knife * server create as
- # well. We want to change the semantics so that the default is false and 'nil' means -P without an argument on the
- # command line. But the other utilities expect nil to be the default and we can't prompt in that case. So we effectively
- # use ssh_password_ng to determine if we're coming from knife ssh or from the other utilities. The other utilties can
- # also be patched to use ssh_password_ng easily as long they follow the convention that the default is false.
def configure_password
- if config.has_key?(:ssh_password_ng) && config[:ssh_password_ng].nil?
- # If the parameter is called on the command line with no value
- # it will set :ssh_password_ng = nil
- # This is where we want to trigger a prompt for password
+ if config.key?(:ssh_password) && config[:ssh_password].nil?
+ # if we have an actual nil that means someone called "--ssh-password" with no value, so we prompt for a password
config[:ssh_password] = get_password
else
- # if ssh_password_ng is false then it has not been set at all, and we may be in knife ec2 and still
- # using an old config[:ssh_password]. this is backwards compatibility. all knife cloud plugins should
- # be updated to use ssh_password_ng with a default of false and ssh_password should be retired, (but
- # we'll still need to use the ssh_password out of knife.rb if we find that).
- ssh_password = config.has_key?(:ssh_password_ng) ? config[:ssh_password_ng] : config[:ssh_password]
- # Otherwise, the password has either been specified on the command line,
- # in knife.rb, or key based auth will be attempted
- config[:ssh_password] = get_stripped_unfrozen_value(ssh_password ||
- Chef::Config[:knife][:ssh_password])
+ # the false default of ssh_password results in a nil here
+ config[:ssh_password] = get_stripped_unfrozen_value(config[:ssh_password])
end
end
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])
+ config[:ssh_identity_file] = get_stripped_unfrozen_value(config[:ssh_identity_file])
end
- def extract_nested_value(data_structure, path_spec)
- ui.presenter.extract_nested_value(data_structure, path_spec)
+ def configure_ssh_gateway_identity
+ config[:ssh_gateway_identity] = get_stripped_unfrozen_value(config[:ssh_gateway_identity])
end
def run
@longest = 0
+ if @name_args.length < 1
+ show_usage
+ ui.fatal("You must specify the SEARCH QUERY.")
+ exit(1)
+ end
+
configure_user
configure_password
- configure_ssh_identity_file
+ @password = config[:ssh_password] if config[:ssh_password]
+
+ # If a password was not given, check for SSH identity file.
+ unless @password
+ configure_ssh_identity_file
+ configure_ssh_gateway_identity
+ end
+
configure_gateway
configure_session
@@ -557,16 +626,12 @@ class Chef
macterm
when "cssh"
cssh
- when "csshx"
- Chef::Log.warn("knife ssh csshx will be deprecated in a future release")
- Chef::Log.warn("please use knife ssh cssh instead")
- cssh
else
- ssh_command(@name_args[1..-1].join(" "))
+ ssh_command(@name_args[1..].join(" "))
end
session.close
- if exit_status != 0
+ if exit_status && exit_status != 0
exit exit_status
else
exit_status
diff --git a/lib/chef/knife/ssl_check.rb b/lib/chef/knife/ssl_check.rb
index 82ccb76ad7..0cc4141d42 100644
--- a/lib/chef/knife/ssl_check.rb
+++ b/lib/chef/knife/ssl_check.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,20 +16,21 @@
# limitations under the License.
#
-require "chef/knife"
-require "chef/config"
+require_relative "../knife"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Knife
class SslCheck < Chef::Knife
deps do
- require "pp"
- require "socket"
- require "uri"
- require "chef/http/ssl_policies"
- require "openssl"
- require "chef/mixin/proxified_socket"
+ require_relative "../config"
+ require "pp" unless defined?(PP)
+ require "socket" unless defined?(Socket)
+ require "uri" unless defined?(URI)
+ require_relative "../http/ssl_policies"
+ require "openssl" unless defined?(OpenSSL)
+ require_relative "../mixin/proxified_socket"
include Chef::Mixin::ProxifiedSocket
end
@@ -44,7 +45,7 @@ class Chef
def uri
@uri ||= begin
- Chef::Log.debug("Checking SSL cert on #{given_uri}")
+ Chef::Log.trace("Checking SSL cert on #{given_uri}")
URI.parse(given_uri)
end
end
@@ -131,7 +132,7 @@ class Chef
true
rescue OpenSSL::SSL::SSLError => e
ui.error "The SSL certificate of #{host} could not be verified"
- Chef::Log.debug e.message
+ Chef::Log.trace e.message
debug_invalid_cert
false
end
@@ -141,7 +142,7 @@ class Chef
true
rescue OpenSSL::SSL::SSLError => e
ui.error "The SSL cert is signed by a trusted authority but is not valid for the given hostname"
- Chef::Log.debug(e)
+ Chef::Log.trace(e)
debug_invalid_host
false
end
@@ -151,27 +152,27 @@ class Chef
debug_ssl_settings
debug_chef_ssl_config
- ui.warn(<<-BAD_CERTS)
-There are invalid certificates in your trusted_certs_dir.
-OpenSSL will not use the following certificates when verifying SSL connections:
+ ui.warn(<<~BAD_CERTS)
+ There are invalid certificates in your trusted_certs_dir.
+ OpenSSL will not use the following certificates when verifying SSL connections:
-#{cert_debug_msg}
+ #{cert_debug_msg}
-#{ui.color("TO FIX THESE WARNINGS:", :bold)}
+ #{ui.color("TO FIX THESE WARNINGS:", :bold)}
-We are working on documentation for resolving common issues uncovered here.
+ We are working on documentation for resolving common issues uncovered here.
-* If the certificate is generated by the server, you may try redownloading the
-server's certificate. By default, the certificate is stored in the following
-location on the host where your chef-server runs:
+ * If the certificate is generated by the server, you may try redownloading the
+ server's certificate. By default, the certificate is stored in the following
+ location on the host where your chef-server runs:
- /var/opt/opscode/nginx/ca/SERVER_HOSTNAME.crt
+ /var/opt/opscode/nginx/ca/SERVER_HOSTNAME.crt
-Copy that file to your trusted_certs_dir (currently: #{configuration.trusted_certs_dir})
-using SSH/SCP or some other secure method, then re-run this command to confirm
-that the server's certificate is now trusted.
+ Copy that file to your trusted_certs_dir (currently: #{configuration.trusted_certs_dir})
+ using SSH/SCP or some other secure method, then re-run this command to confirm
+ that the server's certificate is now trusted.
-BAD_CERTS
+ BAD_CERTS
# @TODO: ^ needs URL once documentation is posted.
end
@@ -184,23 +185,23 @@ BAD_CERTS
debug_ssl_settings
debug_chef_ssl_config
- ui.err(<<-ADVICE)
+ ui.err(<<~ADVICE)
-#{ui.color("TO FIX THIS ERROR:", :bold)}
+ #{ui.color("TO FIX THIS ERROR:", :bold)}
-If the server you are connecting to uses a self-signed certificate, you must
-configure chef to trust that server's certificate.
+ If the server you are connecting to uses a self-signed certificate, you must
+ configure #{ChefUtils::Dist::Infra::PRODUCT} to trust that server's certificate.
-By default, the certificate is stored in the following location on the host
-where your chef-server runs:
+ By default, the certificate is stored in the following location on the host
+ where your chef-server runs:
- /var/opt/opscode/nginx/ca/SERVER_HOSTNAME.crt
+ /var/opt/opscode/nginx/ca/SERVER_HOSTNAME.crt
-Copy that file to your trusted_certs_dir (currently: #{configuration.trusted_certs_dir})
-using SSH/SCP or some other secure method, then re-run this command to confirm
-that the server's certificate is now trusted.
+ Copy that file to your trusted_certs_dir (currently: #{configuration.trusted_certs_dir})
+ using SSH/SCP or some other secure method, then re-run this command to confirm
+ that the server's certificate is now trusted.
-ADVICE
+ ADVICE
end
def debug_invalid_host
@@ -211,18 +212,18 @@ ADVICE
ui.error("You are attempting to connect to: '#{host}'")
ui.error("The server's certificate belongs to '#{cn}'")
- ui.err(<<-ADVICE)
+ ui.err(<<~ADVICE)
-#{ui.color("TO FIX THIS ERROR:", :bold)}
+ #{ui.color("TO FIX THIS ERROR:", :bold)}
-The solution for this issue depends on your networking configuration. If you
-are able to connect to this server using the hostname #{cn}
-instead of #{host}, then you can resolve this issue by updating chef_server_url
-in your configuration file.
+ The solution for this issue depends on your networking configuration. If you
+ are able to connect to this server using the hostname #{cn}
+ instead of #{host}, then you can resolve this issue by updating chef_server_url
+ in your configuration file.
-If you are not able to connect to the server using the hostname #{cn}
-you will have to update the certificate on the server to use the correct hostname.
-ADVICE
+ If you are not able to connect to the server using the hostname #{cn}
+ you will have to update the certificate on the server to use the correct hostname.
+ ADVICE
end
def debug_ssl_settings
@@ -233,7 +234,7 @@ ADVICE
end
def debug_chef_ssl_config
- ui.err "Chef SSL Configuration:"
+ ui.err "#{ChefUtils::Dist::Infra::PRODUCT} SSL Configuration:"
ui.err "* ssl_ca_path: #{configuration.ssl_ca_path.inspect}"
ui.err "* ssl_ca_file: #{configuration.ssl_ca_file.inspect}"
ui.err "* trusted_certs_dir: #{configuration.trusted_certs_dir.inspect}"
@@ -276,7 +277,7 @@ ADVICE
rescue OpenSSL::X509::StoreError => e
return e.message
end
- return nil
+ nil
end
end
end
diff --git a/lib/chef/knife/ssl_fetch.rb b/lib/chef/knife/ssl_fetch.rb
index 5af1a905d5..cfbbc823b2 100644
--- a/lib/chef/knife/ssl_fetch.rb
+++ b/lib/chef/knife/ssl_fetch.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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/knife"
-require "chef/config"
+require_relative "../knife"
class Chef
class Knife
class SslFetch < Chef::Knife
deps do
- require "pp"
- require "socket"
- require "uri"
- require "openssl"
- require "chef/mixin/proxified_socket"
+ require_relative "../config"
+ require "pp" unless defined?(PP)
+ require "socket" unless defined?(Socket)
+ require "uri" unless defined?(URI)
+ require "openssl" unless defined?(OpenSSL)
+ require_relative "../mixin/proxified_socket"
include Chef::Mixin::ProxifiedSocket
end
@@ -41,7 +41,7 @@ class Chef
def uri
@uri ||= begin
- Chef::Log.debug("Checking SSL cert on #{given_uri}")
+ Chef::Log.trace("Checking SSL cert on #{given_uri}")
URI.parse(given_uri)
end
end
@@ -89,15 +89,18 @@ class Chef
def cn_of(certificate)
subject = certificate.subject
- cn_field_tuple = subject.to_a.find { |field| field[0] == "CN" }
- cn_field_tuple[1]
+ if cn_field_tuple = subject.to_a.find { |field| field[0] == "CN" }
+ cn_field_tuple[1]
+ else
+ nil
+ end
end
# Convert the CN of a certificate into something that will work well as a
# filename. To do so, all `*` characters are converted to the string
- # "wildcard" and then all characters other than alphanumeric and hypen
+ # "wildcard" and then all characters other than alphanumeric and hyphen
# characters are converted to underscores.
- # NOTE: There is some confustion about what the CN will contain when
+ # NOTE: There is some confusion about what the CN will contain when
# using internationalized domain names. RFC 6125 mandates that the ascii
# representation be used, but it is not clear whether this is followed in
# practice.
@@ -117,23 +120,24 @@ class Chef
def write_cert(cert)
FileUtils.mkdir_p(trusted_certs_dir)
cn = cn_of(cert)
- filename = File.join(trusted_certs_dir, "#{normalize_cn(cn)}.crt")
- ui.msg("Adding certificate for #{cn} in #{filename}")
- File.open(filename, File::CREAT | File::TRUNC | File::RDWR, 0644) do |f|
+ filename = cn.nil? ? "#{host}_#{Time.new.to_i}" : normalize_cn(cn)
+ full_path = File.join(trusted_certs_dir, "#{filename}.crt")
+ ui.msg("Adding certificate for #{filename} in #{full_path}")
+ File.open(full_path, File::CREAT | File::TRUNC | File::RDWR, 0644) do |f|
f.print(cert.to_s)
end
end
def run
validate_uri
- ui.warn(<<-TRUST_TRUST)
-Certificates from #{host} will be fetched and placed in your trusted_cert
-directory (#{trusted_certs_dir}).
+ ui.warn(<<~TRUST_TRUST)
+ Certificates from #{host} will be fetched and placed in your trusted_cert
+ directory (#{trusted_certs_dir}).
-Knife has no means to verify these are the correct certificates. You should
-verify the authenticity of these certificates after downloading.
+ Knife has no means to verify these are the correct certificates. You should
+ verify the authenticity of these certificates after downloading.
-TRUST_TRUST
+ TRUST_TRUST
remote_cert_chain.each do |cert|
write_cert(cert)
end
diff --git a/lib/chef/knife/status.rb b/lib/chef/knife/status.rb
index 0e3cd7e7d6..34692d6da7 100644
--- a/lib/chef/knife/status.rb
+++ b/lib/chef/knife/status.rb
@@ -1,6 +1,6 @@
#
# Author:: Ian Meyer (<ianmmeyer@gmail.com>)
-# Copyright:: Copyright 2010-2016, Ian Meyer
+# Copyright:: Copyright 2010-2020, Ian Meyer
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,39 +16,35 @@
# limitations under the License.
#
-require "chef/knife"
-require "chef/knife/core/status_presenter"
-require "chef/knife/core/node_presenter"
+require_relative "../knife"
+require_relative "core/status_presenter"
+require_relative "core/formatting_options"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Knife
class Status < Knife
- include Knife::Core::NodeFormattingOptions
+ include Knife::Core::FormattingOptions
deps do
- require "chef/search/query"
+ require_relative "../search/query"
end
banner "knife status QUERY (options)"
option :run_list,
- :short => "-r",
- :long => "--run-list",
- :description => "Show the run list"
+ short: "-r",
+ long: "--run-list",
+ description: "Show the run list"
option :sort_reverse,
- :short => "-s",
- :long => "--sort-reverse",
- :description => "Sort the status list by last run time descending"
-
- option :hide_healthy,
- :short => "-H",
- :long => "--hide-healthy",
- :description => "Hide nodes that have run chef in the last hour. [DEPRECATED] Use --hide-by-mins MINS instead"
+ short: "-s",
+ long: "--sort-reverse",
+ description: "Sort the status list by last run time descending"
option :hide_by_mins,
- :long => "--hide-by-mins MINS",
- :description => "Hide nodes that have run chef in the last MINS minutes"
+ long: "--hide-by-mins MINS",
+ description: "Hide nodes that have run #{ChefUtils::Dist::Infra::CLIENT} in the last MINS minutes"
def append_to_query(term)
@query << " AND " unless @query.empty?
@@ -63,7 +59,7 @@ class Chef
else
opts = { filter_result:
{ name: ["name"], ipaddress: ["ipaddress"], ohai_time: ["ohai_time"],
- ec2: ["ec2"], run_list: ["run_list"], platform: ["platform"],
+ cloud: ["cloud"], run_list: ["run_list"], platform: ["platform"],
platform_version: ["platform_version"], chef_environment: ["chef_environment"] } }
end
@@ -71,20 +67,12 @@ class Chef
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
- # 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
-
if config[:hide_by_mins]
- hidemins = config[:hide_by_mins].to_i
+ hide_by_mins = 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}]"
+ @query << "NOT ohai_time:[#{(time - hide_by_mins * 60)} TO #{time}]"
end
@query = @query.empty? ? "*:*" : @query
@@ -96,13 +84,10 @@ class Chef
all_nodes << node
end
- output(all_nodes.sort do |n1, n2|
- if config[:sort_reverse] || Chef::Config[:knife][:sort_status_reverse]
- (n2["ohai_time"] || 0) <=> (n1["ohai_time"] || 0)
- else
- (n1["ohai_time"] || 0) <=> (n2["ohai_time"] || 0)
- end
- end)
+ all_nodes.sort_by! { |n| n["ohai_time"] || 0 }
+ all_nodes.reverse! if config[:sort_reverse] || config[:sort_status_reverse]
+
+ output(all_nodes)
end
end
diff --git a/lib/chef/knife/supermarket_download.rb b/lib/chef/knife/supermarket_download.rb
index 5657558591..5acd733b78 100644
--- a/lib/chef/knife/supermarket_download.rb
+++ b/lib/chef/knife/supermarket_download.rb
@@ -1,6 +1,6 @@
#
# Author:: Christopher Webber (<cwebber@chef.io>)
-# Copyright:: Copyright (c) 2014 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,18 +16,106 @@
# limitations under the License.
#
-require "chef/knife"
-require "chef/knife/cookbook_site_download"
+require_relative "../knife"
class Chef
class Knife
- class SupermarketDownload < Knife::CookbookSiteDownload
- # Handle the subclassing (knife doesn't do this :()
- dependency_loaders.concat(superclass.dependency_loaders)
- options.merge!(superclass.options)
+ class SupermarketDownload < Knife
banner "knife supermarket download COOKBOOK [VERSION] (options)"
category "supermarket"
+
+ deps do
+ require "fileutils" unless defined?(FileUtils)
+ end
+
+ option :file,
+ short: "-f FILE",
+ long: "--file FILE",
+ description: "The filename to write to."
+
+ option :force,
+ long: "--force",
+ description: "Force download deprecated version."
+
+ option :supermarket_site,
+ short: "-m SUPERMARKET_SITE",
+ long: "--supermarket-site SUPERMARKET_SITE",
+ description: "The URL of the Supermarket site.",
+ default: "https://supermarket.chef.io"
+
+ def run
+ if current_cookbook_deprecated?
+ message = "DEPRECATION: This cookbook has been deprecated. "
+ replacement = replacement_cookbook
+ if !replacement.to_s.strip.empty?
+ message << "It has been replaced by #{replacement}."
+ else
+ message << "No replacement has been defined."
+ end
+ ui.warn message
+
+ unless config[:force]
+ ui.warn "Use --force to force download deprecated cookbook."
+ return
+ end
+ end
+
+ download_cookbook
+ end
+
+ def version
+ @version = desired_cookbook_data["version"]
+ end
+
+ private
+
+ def cookbooks_api_url
+ "#{config[:supermarket_site]}/api/v1/cookbooks"
+ end
+
+ def current_cookbook_data
+ @current_cookbook_data ||= begin
+ noauth_rest.get "#{cookbooks_api_url}/#{@name_args[0]}"
+ end
+ end
+
+ def current_cookbook_deprecated?
+ 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 uri
+ end
+ end
+
+ def download_cookbook
+ ui.info "Downloading #{@name_args[0]} from Supermarket at version #{version} to #{download_location}"
+ tf = noauth_rest.streaming_request(desired_cookbook_data["file"])
+
+ ::FileUtils.cp tf.path, download_location
+ ui.info "Cookbook saved: #{download_location}"
+ end
+
+ def download_location
+ config[:file] ||= File.join Dir.pwd, "#{@name_args[0]}-#{version}.tar.gz"
+ config[:file]
+ end
+
+ def replacement_cookbook
+ File.basename(current_cookbook_data["replacement"] || "")
+ end
+
+ def specific_cookbook_version_url
+ "#{cookbooks_api_url}/#{@name_args[0]}/versions/#{@name_args[1].tr(".", "_")}"
+ end
end
end
end
diff --git a/lib/chef/knife/supermarket_install.rb b/lib/chef/knife/supermarket_install.rb
index 7642e68181..a3d3aa7a5d 100644
--- a/lib/chef/knife/supermarket_install.rb
+++ b/lib/chef/knife/supermarket_install.rb
@@ -1,6 +1,6 @@
#
# Author:: Christopher Webber (<cwebber@chef.io>)
-# Copyright:: Copyright (c) 2014 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,18 +16,177 @@
# limitations under the License.
#
-require "chef/knife"
-require "chef/knife/cookbook_site_install"
+require_relative "../knife"
class Chef
class Knife
- class SupermarketInstall < Knife::CookbookSiteInstall
- # Handle the subclassing (knife doesn't do this :()
- dependency_loaders.concat(superclass.dependency_loaders)
- options.merge!(superclass.options)
+ class SupermarketInstall < Knife
+
+ deps do
+ require_relative "../exceptions"
+ require "shellwords" unless defined?(Shellwords)
+ require "mixlib/archive" unless defined?(Mixlib::Archive)
+ require_relative "core/cookbook_scm_repo"
+ require_relative "../cookbook/metadata"
+ end
banner "knife supermarket install COOKBOOK [VERSION] (options)"
category "supermarket"
+
+ option :no_deps,
+ short: "-D",
+ long: "--skip-dependencies",
+ boolean: true,
+ default: false,
+ description: "Skips automatic dependency installation."
+
+ option :cookbook_path,
+ short: "-o PATH:PATH",
+ long: "--cookbook-path PATH:PATH",
+ description: "A colon-separated path to look for cookbooks in.",
+ proc: lambda { |o| o.split(":") }
+
+ option :default_branch,
+ short: "-B BRANCH",
+ long: "--branch BRANCH",
+ description: "Default branch to work with.",
+ default: "master"
+
+ option :use_current_branch,
+ short: "-b",
+ long: "--use-current-branch",
+ description: "Use the current branch.",
+ boolean: true,
+ default: false
+
+ option :supermarket_site,
+ short: "-m SUPERMARKET_SITE",
+ long: "--supermarket-site SUPERMARKET_SITE",
+ description: "The URL of the Supermarket site.",
+ default: "https://supermarket.chef.io"
+
+ attr_reader :cookbook_name
+ attr_reader :vendor_path
+
+ def run
+ if config[:cookbook_path]
+ Chef::Config[:cookbook_path] = config[:cookbook_path]
+ else
+ config[:cookbook_path] = Chef::Config[:cookbook_path]
+ end
+
+ @cookbook_name = parse_name_args!
+ # Check to ensure we have a valid source of cookbooks before continuing
+ #
+ @install_path = File.expand_path(Array(config[:cookbook_path]).first)
+ ui.info "Installing #{@cookbook_name} to #{@install_path}"
+
+ @repo = CookbookSCMRepo.new(@install_path, ui, config)
+ # cookbook_path = File.join(vendor_path, name_args[0])
+ upstream_file = File.join(@install_path, "#{@cookbook_name}.tar.gz")
+
+ @repo.sanity_check
+ unless config[:use_current_branch]
+ @repo.reset_to_default_state
+ @repo.prepare_to_import(@cookbook_name)
+ end
+
+ downloader = download_cookbook_to(upstream_file)
+ clear_existing_files(File.join(@install_path, @cookbook_name))
+ extract_cookbook(upstream_file, downloader.version)
+
+ # TODO: it'd be better to store these outside the cookbook repo and
+ # keep them around, e.g., in ~/Library/Caches on macOS.
+ ui.info("Removing downloaded tarball")
+ File.unlink(upstream_file)
+
+ if @repo.finalize_updates_to(@cookbook_name, downloader.version)
+ unless config[:use_current_branch]
+ @repo.reset_to_default_state
+ end
+ @repo.merge_updates_from(@cookbook_name, downloader.version)
+ else
+ unless config[:use_current_branch]
+ @repo.reset_to_default_state
+ end
+ end
+
+ unless config[:no_deps]
+ preferred_metadata.dependencies.each_key do |cookbook|
+ # Doesn't do versions.. yet
+ nv = self.class.new
+ nv.config = config
+ nv.name_args = [ cookbook ]
+ nv.run
+ end
+ end
+ end
+
+ def parse_name_args!
+ if name_args.empty?
+ ui.error("Please specify a cookbook to download and install.")
+ exit 1
+ elsif name_args.size >= 2
+ unless name_args.last.match(/^(\d+)(\.\d+){1,2}$/) && name_args.size == 2
+ ui.error("Installing multiple cookbooks at once is not supported.")
+ exit 1
+ end
+ end
+ name_args.first
+ end
+
+ def download_cookbook_to(download_path)
+ downloader = Chef::Knife::SupermarketDownload.new
+ downloader.config[:file] = download_path
+ downloader.config[:supermarket_site] = config[:supermarket_site]
+ downloader.name_args = name_args
+ downloader.run
+ downloader
+ end
+
+ def extract_cookbook(upstream_file, version)
+ ui.info("Uncompressing #{@cookbook_name} version #{version}.")
+ Mixlib::Archive.new(convert_path(upstream_file)).extract(@install_path, perms: false)
+ end
+
+ def clear_existing_files(cookbook_path)
+ ui.info("Removing pre-existing version.")
+ FileUtils.rmtree(cookbook_path) if File.directory?(cookbook_path)
+ end
+
+ def convert_path(upstream_file)
+ # converts a Windows path (C:\foo) to a mingw path (/c/foo)
+ if ENV["MSYSTEM"] == "MINGW32"
+ upstream_file.sub(/^([[:alpha:]]):/, '/\1')
+ else
+ Shellwords.escape upstream_file
+ end
+ end
+
+ # Get the preferred metadata path on disk. Chef prefers the metadata.rb
+ # over the metadata.json.
+ #
+ # @raise if there is no metadata in the cookbook
+ #
+ # @return [Chef::Cookbook::Metadata]
+ def preferred_metadata
+ md = Chef::Cookbook::Metadata.new
+
+ rb = File.join(@install_path, @cookbook_name, "metadata.rb")
+ if File.exist?(rb)
+ md.from_file(rb)
+ return md
+ end
+
+ json = File.join(@install_path, @cookbook_name, "metadata.json")
+ if File.exist?(json)
+ json = IO.read(json)
+ md.from_json(json)
+ return md
+ end
+
+ raise Chef::Exceptions::MetadataNotFound.new(@install_path, @cookbook_name)
+ end
end
end
end
diff --git a/lib/chef/knife/supermarket_list.rb b/lib/chef/knife/supermarket_list.rb
index f2bc98bd0e..7dca8d031b 100644
--- a/lib/chef/knife/supermarket_list.rb
+++ b/lib/chef/knife/supermarket_list.rb
@@ -1,6 +1,6 @@
#
# Author:: Christopher Webber (<cwebber@chef.io>)
-# Copyright:: Copyright (c) 2014 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,18 +16,61 @@
# limitations under the License.
#
-require "chef/knife"
-require "chef/knife/cookbook_site_list"
+require_relative "../knife"
class Chef
class Knife
- class SupermarketList < Knife::CookbookSiteList
- # Handle the subclassing (knife doesn't do this :()
- dependency_loaders.concat(superclass.dependency_loaders)
- options.merge!(superclass.options)
+ class SupermarketList < Knife
banner "knife supermarket list (options)"
category "supermarket"
+
+ option :with_uri,
+ short: "-w",
+ long: "--with-uri",
+ description: "Show corresponding URIs."
+
+ option :supermarket_site,
+ short: "-m SUPERMARKET_SITE",
+ long: "--supermarket-site SUPERMARKET_SITE",
+ description: "The URL of the Supermarket site.",
+ default: "https://supermarket.chef.io"
+
+ option :sort_by,
+ long: "--sort-by SORT",
+ description: "Use to sort the records",
+ in: %w{recently_updated recently_added most_downloaded most_followed}
+
+ option :owned_by,
+ short: "-o USER",
+ long: "--owned-by USER",
+ description: "Show cookbooks that are owned by the USER"
+
+ def run
+ if config[:with_uri]
+ ui.output(format_for_display(get_cookbook_list))
+ else
+ ui.msg(ui.list(get_cookbook_list.keys, :columns_down))
+ end
+ end
+
+ # In order to avoid pagination items limit set to 9999999
+ def get_cookbook_list(items = 9999999, start = 0, cookbook_collection = {})
+ cookbooks_url = "#{config[:supermarket_site]}/api/v1/cookbooks?items=#{items}&start=#{start}"
+ cookbooks_url << "&order=#{config[:sort_by]}" if config[:sort_by]
+ cookbooks_url << "&user=#{config[:owned_by]}" if config[:owned_by]
+ cr = noauth_rest.get(cookbooks_url)
+
+ cr["items"].each do |cookbook|
+ cookbook_collection[cookbook["cookbook_name"]] = cookbook["cookbook"]
+ end
+ new_start = start + items
+ if new_start < cr["total"]
+ get_cookbook_list(items, new_start, cookbook_collection)
+ else
+ cookbook_collection
+ end
+ end
end
end
end
diff --git a/lib/chef/knife/supermarket_search.rb b/lib/chef/knife/supermarket_search.rb
index 3206b0cb80..57befaed35 100644
--- a/lib/chef/knife/supermarket_search.rb
+++ b/lib/chef/knife/supermarket_search.rb
@@ -1,6 +1,6 @@
#
# Author:: Christopher Webber (<cwebber@chef.io>)
-# Copyright:: Copyright (c) 2014 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,18 +16,38 @@
# limitations under the License.
#
-require "chef/knife"
-require "chef/knife/cookbook_site_search"
+require_relative "../knife"
class Chef
class Knife
- class SupermarketSearch < Knife::CookbookSiteSearch
- # Handle the subclassing (knife doesn't do this :()
- dependency_loaders.concat(superclass.dependency_loaders)
- options.merge!(superclass.options)
-
+ class SupermarketSearch < Knife
banner "knife supermarket search QUERY (options)"
category "supermarket"
+
+ option :supermarket_site,
+ short: "-m SUPERMARKET_SITE",
+ long: "--supermarket-site SUPERMARKET_SITE",
+ description: "The URL of the Supermarket site.",
+ default: "https://supermarket.chef.io"
+
+ def run
+ output(search_cookbook(name_args[0]))
+ end
+
+ # In order to avoid pagination items limit set to 9999999
+ def search_cookbook(query, items = 9999999, start = 0, cookbook_collection = {})
+ cookbooks_url = "#{config[:supermarket_site]}/api/v1/search?q=#{query}&items=#{items}&start=#{start}"
+ cr = noauth_rest.get(cookbooks_url)
+ cr["items"].each do |cookbook|
+ cookbook_collection[cookbook["cookbook_name"]] = cookbook
+ end
+ new_start = start + items
+ if new_start < cr["total"]
+ search_cookbook(query, items, new_start, cookbook_collection)
+ else
+ cookbook_collection
+ end
+ end
end
end
end
diff --git a/lib/chef/knife/supermarket_share.rb b/lib/chef/knife/supermarket_share.rb
index 3109b9e794..49b3474566 100644
--- a/lib/chef/knife/supermarket_share.rb
+++ b/lib/chef/knife/supermarket_share.rb
@@ -1,6 +1,6 @@
#
# Author:: Christopher Webber (<cwebber@chef.io>)
-# Copyright:: Copyright (c) 2014 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,18 +16,151 @@
# limitations under the License.
#
-require "chef/knife"
-require "chef/knife/cookbook_site_share"
+require_relative "../knife"
class Chef
class Knife
- class SupermarketShare < Knife::CookbookSiteShare
- # Handle the subclassing (knife doesn't do this :()
- dependency_loaders.concat(superclass.dependency_loaders)
- options.merge!(superclass.options)
+ class SupermarketShare < Knife
+
+ include Chef::Mixin::ShellOut
+
+ deps do
+ require_relative "../cookbook_loader"
+ require_relative "../cookbook_uploader"
+ require_relative "../cookbook_site_streaming_uploader"
+ require_relative "../mixin/shell_out"
+ end
banner "knife supermarket share COOKBOOK [CATEGORY] (options)"
category "supermarket"
+
+ option :cookbook_path,
+ short: "-o PATH:PATH",
+ long: "--cookbook-path PATH:PATH",
+ description: "A colon-separated path to look for cookbooks in.",
+ proc: lambda { |o| Chef::Config.cookbook_path = o.split(":") }
+
+ option :dry_run,
+ long: "--dry-run",
+ short: "-n",
+ boolean: true,
+ default: false,
+ description: "Don't take action, only print what files will be uploaded to Supermarket."
+
+ option :supermarket_site,
+ short: "-m SUPERMARKET_SITE",
+ long: "--supermarket-site SUPERMARKET_SITE",
+ description: "The URL of the Supermarket site.",
+ default: "https://supermarket.chef.io"
+
+ def run
+ config[:cookbook_path] ||= Chef::Config[:cookbook_path]
+
+ if @name_args.length < 1
+ show_usage
+ ui.fatal("You must specify the cookbook name.")
+ exit(1)
+ elsif @name_args.length < 2
+ cookbook_name = @name_args[0]
+ category = get_category(cookbook_name)
+ else
+ cookbook_name = @name_args[0]
+ category = @name_args[1]
+ end
+
+ cl = Chef::CookbookLoader.new(config[:cookbook_path])
+ if cl.cookbook_exists?(cookbook_name)
+ cookbook = cl[cookbook_name]
+ Chef::CookbookUploader.new(cookbook).validate_cookbooks
+ tmp_cookbook_dir = Chef::CookbookSiteStreamingUploader.create_build_dir(cookbook)
+ begin
+ Chef::Log.trace("Temp cookbook directory is #{tmp_cookbook_dir.inspect}")
+ ui.info("Making tarball #{cookbook_name}.tgz")
+ shell_out!("#{tar_cmd} -czf #{cookbook_name}.tgz #{cookbook_name}", cwd: tmp_cookbook_dir)
+ rescue => e
+ ui.error("Error making tarball #{cookbook_name}.tgz: #{e.message}. Increase log verbosity (-VV) for more information.")
+ Chef::Log.trace("\n#{e.backtrace.join("\n")}")
+ exit(1)
+ end
+
+ if config[:dry_run]
+ ui.info("Not uploading #{cookbook_name}.tgz due to --dry-run flag.")
+ result = shell_out!("#{tar_cmd} -tzf #{cookbook_name}.tgz", cwd: tmp_cookbook_dir)
+ ui.info(result.stdout)
+ FileUtils.rm_rf tmp_cookbook_dir
+ return
+ end
+
+ begin
+ do_upload("#{tmp_cookbook_dir}/#{cookbook_name}.tgz", category, Chef::Config[:node_name], Chef::Config[:client_key])
+ ui.info("Upload complete")
+ Chef::Log.trace("Removing local staging directory at #{tmp_cookbook_dir}")
+ FileUtils.rm_rf tmp_cookbook_dir
+ rescue => e
+ ui.error("Error uploading cookbook #{cookbook_name} to Supermarket: #{e.message}. Increase log verbosity (-VV) for more information.")
+ Chef::Log.trace("\n#{e.backtrace.join("\n")}")
+ exit(1)
+ end
+
+ else
+ ui.error("Could not find cookbook #{cookbook_name} in your cookbook path.")
+ exit(1)
+ end
+ end
+
+ def get_category(cookbook_name)
+ data = noauth_rest.get("#{config[:supermarket_site]}/api/v1/cookbooks/#{@name_args[0]}")
+ data["category"]
+ rescue => e
+ return "Other" if e.is_a?(Net::HTTPClientException) && e.response.code == "404"
+
+ ui.fatal("Unable to reach Supermarket: #{e.message}. Increase log verbosity (-VV) for more information.")
+ Chef::Log.trace("\n#{e.backtrace.join("\n")}")
+ exit(1)
+ end
+
+ def do_upload(cookbook_filename, cookbook_category, user_id, user_secret_filename)
+ uri = "#{config[:supermarket_site]}/api/v1/cookbooks"
+
+ 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,
+ })
+
+ res = Chef::JSONCompat.from_json(http_resp.body)
+ if http_resp.code.to_i != 201
+ if res["error_messages"]
+ if /Version already exists/.match?(res["error_messages"][0])
+ ui.error "The same version of this cookbook already exists on Supermarket."
+ exit(1)
+ else
+ ui.error (res["error_messages"][0]).to_s
+ exit(1)
+ end
+ else
+ ui.error "Unknown error while sharing cookbook"
+ ui.error "Server response: #{http_resp.body}"
+ exit(1)
+ end
+ end
+ res
+ end
+
+ def tar_cmd
+ unless @tar_cmd
+ @tar_cmd = "tar"
+ begin
+ # Unix and Mac only - prefer gnutar
+ if shell_out("which gnutar").exitstatus.equal?(0)
+ @tar_cmd = "gnutar"
+ end
+ rescue Errno::ENOENT
+ end
+ end
+ @tar_cmd
+ end
end
end
end
diff --git a/lib/chef/knife/supermarket_show.rb b/lib/chef/knife/supermarket_show.rb
index 2ad122143f..7237cf0bc7 100644
--- a/lib/chef/knife/supermarket_show.rb
+++ b/lib/chef/knife/supermarket_show.rb
@@ -1,6 +1,6 @@
#
# Author:: Christopher Webber (<cwebber@chef.io>)
-# Copyright:: Copyright (c) 2014 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,18 +16,51 @@
# limitations under the License.
#
-require "chef/knife"
-require "chef/knife/cookbook_site_show"
+require_relative "../knife"
class Chef
class Knife
- class SupermarketShow < Knife::CookbookSiteShow
- # Handle the subclassing (knife doesn't do this :()
- dependency_loaders.concat(superclass.dependency_loaders)
- options.merge!(superclass.options)
+ class SupermarketShow < Knife
banner "knife supermarket show COOKBOOK [VERSION] (options)"
category "supermarket"
+
+ option :supermarket_site,
+ short: "-m SUPERMARKET_SITE",
+ long: "--supermarket-site SUPERMARKET_SITE",
+ description: "The URL of the Supermarket site.",
+ default: "https://supermarket.chef.io"
+
+ def run
+ output(format_for_display(get_cookbook_data))
+ end
+
+ def supermarket_uri
+ "#{config[:supermarket_site]}/api/v1"
+ end
+
+ def get_cookbook_data
+ case @name_args.length
+ when 1
+ noauth_rest.get("#{supermarket_uri}/cookbooks/#{@name_args[0]}")
+ when 2
+ noauth_rest.get("#{supermarket_uri}/cookbooks/#{@name_args[0]}/versions/#{name_args[1].tr(".", "_")}")
+ end
+ end
+
+ def get_cookbook_list(items = 10, start = 0, cookbook_collection = {})
+ cookbooks_url = "#{supermarket_uri}/cookbooks?items=#{items}&start=#{start}"
+ cr = noauth_rest.get(cookbooks_url)
+ cr["items"].each do |cookbook|
+ cookbook_collection[cookbook["cookbook_name"]] = cookbook
+ end
+ new_start = start + cr["items"].length
+ if new_start < cr["total"]
+ get_cookbook_list(items, new_start, cookbook_collection)
+ else
+ cookbook_collection
+ end
+ end
end
end
end
diff --git a/lib/chef/knife/supermarket_unshare.rb b/lib/chef/knife/supermarket_unshare.rb
index fd48e172ce..686d95f47a 100644
--- a/lib/chef/knife/supermarket_unshare.rb
+++ b/lib/chef/knife/supermarket_unshare.rb
@@ -1,6 +1,6 @@
#
# Author:: Christopher Webber (<cwebber@chef.io>)
-# Copyright:: Copyright (c) 2014 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,18 +16,46 @@
# limitations under the License.
#
-require "chef/knife"
-require "chef/knife/cookbook_site_unshare"
+require_relative "../knife"
class Chef
class Knife
- class SupermarketUnshare < Knife::CookbookSiteUnshare
- # Handle the subclassing (knife doesn't do this :()
- dependency_loaders.concat(superclass.dependency_loaders)
- options.merge!(superclass.options)
+ class SupermarketUnshare < Knife
- banner "knife supermarket unshare COOKBOOK (options)"
+ deps do
+ require_relative "../json_compat"
+ end
+
+ banner "knife supermarket unshare COOKBOOK"
category "supermarket"
+
+ option :supermarket_site,
+ short: "-m SUPERMARKET_SITE",
+ long: "--supermarket-site SUPERMARKET_SITE",
+ description: "The URL of the Supermarket site.",
+ default: "https://supermarket.chef.io"
+
+ def run
+ @cookbook_name = @name_args[0]
+ if @cookbook_name.nil?
+ show_usage
+ ui.fatal "You must provide the name of the cookbook to unshare"
+ exit 1
+ end
+
+ confirm "Do you really want to unshare all versions of the cookbook #{@cookbook_name}"
+
+ begin
+ rest.delete "#{config[:supermarket_site]}/api/v1/cookbooks/#{@name_args[0]}"
+ rescue Net::HTTPClientException => e
+ raise e unless /Forbidden/.match?(e.message)
+
+ ui.error "Forbidden: You must be the maintainer of #{@cookbook_name} to unshare it."
+ exit 1
+ end
+
+ ui.info "Unshared all versions of the cookbook #{@cookbook_name}"
+ end
end
end
end
diff --git a/lib/chef/knife/tag_create.rb b/lib/chef/knife/tag_create.rb
index e8cfe11303..2f0d302e74 100644
--- a/lib/chef/knife/tag_create.rb
+++ b/lib/chef/knife/tag_create.rb
@@ -18,21 +18,21 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class TagCreate < Knife
deps do
- require "chef/node"
+ require_relative "../node"
end
banner "knife tag create NODE TAG ..."
def run
name = @name_args[0]
- tags = @name_args[1..-1]
+ tags = @name_args[1..]
if name.nil? || tags.nil? || tags.empty?
show_usage
diff --git a/lib/chef/knife/tag_delete.rb b/lib/chef/knife/tag_delete.rb
index d00ec2f60b..85fa6a9e27 100644
--- a/lib/chef/knife/tag_delete.rb
+++ b/lib/chef/knife/tag_delete.rb
@@ -18,21 +18,21 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class TagDelete < Knife
deps do
- require "chef/node"
+ require_relative "../node"
end
banner "knife tag delete NODE TAG ..."
def run
name = @name_args[0]
- tags = @name_args[1..-1]
+ tags = @name_args[1..]
if name.nil? || tags.nil? || tags.empty?
show_usage
@@ -41,7 +41,7 @@ class Chef
end
node = Chef::Node.load name
- deleted_tags = Array.new
+ deleted_tags = []
tags.each do |tag|
unless node.tags.delete(tag).nil?
deleted_tags << tag
diff --git a/lib/chef/knife/tag_list.rb b/lib/chef/knife/tag_list.rb
index 2665e53bf4..8b91034609 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_relative "../knife"
class Chef
class Knife
class TagList < Knife
deps do
- require "chef/node"
+ require_relative "../node"
end
banner "knife tag list NODE"
diff --git a/lib/chef/knife/upload.rb b/lib/chef/knife/upload.rb
index f0ecaaae47..190549d86a 100644
--- a/lib/chef/knife/upload.rb
+++ b/lib/chef/knife/upload.rb
@@ -1,52 +1,68 @@
-require "chef/chef_fs/knife"
+#
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../chef_fs/knife"
class Chef
class Knife
class Upload < Chef::ChefFS::Knife
- banner "knife upload PATTERNS"
+ banner "knife upload PATTERNS (options)"
category "path-based"
deps do
- require "chef/chef_fs/command_line"
+ require_relative "../chef_fs/command_line"
end
option :recurse,
- :long => "--[no-]recurse",
- :boolean => true,
- :default => true,
- :description => "List directories recursively."
+ long: "--[no-]recurse",
+ boolean: true,
+ default: true,
+ description: "List directories recursively."
option :purge,
- :long => "--[no-]purge",
- :boolean => true,
- :default => false,
- :description => "Delete matching local files and directories that do not exist remotely."
+ long: "--[no-]purge",
+ boolean: true,
+ default: false,
+ description: "Delete matching local files and directories that do not exist remotely."
option :force,
- :long => "--[no-]force",
- :boolean => true,
- :default => false,
- :description => "Force upload of files even if they match (quicker for many files). Will overwrite frozen cookbooks."
+ long: "--[no-]force",
+ boolean: true,
+ default: false,
+ description: "Force upload of files even if they match (quicker for many files). Will overwrite frozen cookbooks."
option :freeze,
- :long => "--[no-]freeze",
- :boolean => true,
- :default => false,
- :description => "Freeze cookbooks that get uploaded."
+ long: "--[no-]freeze",
+ boolean: true,
+ default: false,
+ description: "Freeze cookbooks that get uploaded."
option :dry_run,
- :long => "--dry-run",
- :short => "-n",
- :boolean => true,
- :default => false,
- :description => "Don't take action, only print what would happen"
+ long: "--dry-run",
+ short: "-n",
+ boolean: true,
+ default: false,
+ description: "Don't take action, only print what would happen."
option :diff,
- :long => "--[no-]diff",
- :boolean => true,
- :default => true,
- :description => "Turn off to avoid uploading existing files; only new (and possibly deleted) files with --no-diff"
+ long: "--[no-]diff",
+ boolean: true,
+ default: true,
+ description: "Turn off to avoid uploading existing files; only new (and possibly deleted) files with --no-diff."
def run
if name_args.length == 0
diff --git a/lib/chef/knife/user_create.rb b/lib/chef/knife/user_create.rb
index d21afb1059..6d68f3ebbb 100644
--- a/lib/chef/knife/user_create.rb
+++ b/lib/chef/knife/user_create.rb
@@ -1,7 +1,7 @@
#
# Author:: Steven Danna (<steve@chef.io>)
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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/osc_user_create"
+require_relative "../knife"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Knife
@@ -27,36 +27,23 @@ class Chef
attr_accessor :user_field
deps do
- require "chef/user_v1"
- require "chef/json_compat"
+ require_relative "../user_v1"
end
option :file,
- :short => "-f FILE",
- :long => "--file FILE",
- :description => "Write the private key to a file if the server generated one."
+ short: "-f FILE",
+ long: "--file 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)."
+ 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 (Chef Server 12.1+) 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 => "DEPRECATED: Open Source Chef 11 only. Create the user as an admin.",
- :boolean => true
-
- option :user_password,
- :short => "-p PASSWORD",
- :long => "--password PASSWORD",
- :description => "DEPRECATED: Open Source Chef 11 only. Password for newly created user.",
- :default => ""
+ short: "-k",
+ long: "--prevent-keygen",
+ description: "API V1 (#{ChefUtils::Dist::Server::PRODUCT} 12.1+) only. Prevent server from generating a default key pair for you. Cannot be passed with --user-key.",
+ boolean: true
banner "knife user create USERNAME DISPLAY_NAME FIRST_NAME LAST_NAME EMAIL PASSWORD (options)"
@@ -68,80 +55,50 @@ class Chef
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
-
- 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
- # 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
+ test_mandatory_field(@name_args[0], "username")
+ user.username @name_args[0]
- test_mandatory_field(@name_args[0], "username")
- user.username @name_args[0]
+ test_mandatory_field(@name_args[1], "display name")
+ user.display_name @name_args[1]
- test_mandatory_field(@name_args[1], "display name")
- user.display_name @name_args[1]
+ test_mandatory_field(@name_args[2], "first name")
+ user.first_name @name_args[2]
- test_mandatory_field(@name_args[2], "first name")
- user.first_name @name_args[2]
+ test_mandatory_field(@name_args[3], "last name")
+ user.last_name @name_args[3]
- 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[4], "email")
- user.email @name_args[4]
+ test_mandatory_field(@name_args[5], "password")
+ user.password @name_args[5]
- 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[: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
- if !config[:prevent_keygen] && !config[:user_key]
- user.create_key(true)
- end
+ if config[:user_key]
+ user.public_key File.read(File.expand_path(config[:user_key]))
+ end
- if config[:user_key]
- user.public_key File.read(File.expand_path(config[:user_key]))
- end
+ output = edit_hash(user)
+ final_user = create_user_from_hash(output)
- output = edit_hash(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
+ 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
end
end
diff --git a/lib/chef/knife/user_delete.rb b/lib/chef/knife/user_delete.rb
index ce4575ceab..87c1f734bb 100644
--- a/lib/chef/knife/user_delete.rb
+++ b/lib/chef/knife/user_delete.rb
@@ -1,6 +1,6 @@
#
# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,53 +16,18 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class UserDelete < Knife
deps do
- require "chef/user_v1"
- require "chef/json_compat"
+ require_relative "../user_v1"
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]
@@ -72,23 +37,7 @@ EOF
exit 1
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
+ delete_object(Chef::UserV1, @user_name)
end
end
end
diff --git a/lib/chef/knife/osc_user_list.rb b/lib/chef/knife/user_dissociate.rb
index f1002c4f54..6af1559608 100644
--- a/lib/chef/knife/osc_user_list.rb
+++ b/lib/chef/knife/user_dissociate.rb
@@ -1,6 +1,6 @@
#
# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,31 +16,26 @@
# 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.
+require_relative "../knife"
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"
+ class UserDissociate < Chef::Knife
+ category "user"
+ banner "knife user dissociate USERNAMES"
def run
- output(format_list_for_display(Chef::User.list))
+ if name_args.length < 1
+ show_usage
+ ui.fatal("You must specify a username.")
+ exit 1
+ end
+ users = name_args
+ ui.confirm("Are you sure you want to dissociate the following users: #{users.join(", ")}")
+ users.each do |u|
+ api_endpoint = "users/#{u}"
+ rest.delete_rest(api_endpoint)
+ end
end
end
end
diff --git a/lib/chef/knife/user_edit.rb b/lib/chef/knife/user_edit.rb
index bb80ede267..ad9dfac079 100644
--- a/lib/chef/knife/user_edit.rb
+++ b/lib/chef/knife/user_edit.rb
@@ -1,6 +1,6 @@
#
# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,37 +16,18 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class UserEdit < Knife
deps do
- require "chef/user_v1"
- require "chef/json_compat"
+ require_relative "../user_v1"
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]
@@ -57,23 +38,13 @@ EOF
end
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_hash(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
+ edited_user = edit_hash(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
diff --git a/lib/chef/knife/osc_user_delete.rb b/lib/chef/knife/user_invite_add.rb
index 51abc1c668..1690147535 100644
--- a/lib/chef/knife/osc_user_delete.rb
+++ b/lib/chef/knife/user_invite_add.rb
@@ -1,6 +1,6 @@
#
# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,36 +16,28 @@
# 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.
+require_relative "../knife"
class Chef
class Knife
- class OscUserDelete < Knife
-
- deps do
- require "chef/user"
- require "chef/json_compat"
- end
-
- banner "knife osc_user delete USER (options)"
+ class UserInviteAdd < Chef::Knife
+ category "user"
+ banner "knife user invite add USERNAMES"
def run
- @user_name = @name_args[0]
-
- if @user_name.nil?
+ if name_args.length < 1
show_usage
- ui.fatal("You must specify a user name")
+ ui.fatal("You must specify a username.")
exit 1
end
- delete_object(Chef::User, @user_name)
+ users = name_args
+ api_endpoint = "association_requests/"
+ users.each do |u|
+ body = { user: u }
+ rest.post_rest(api_endpoint, body)
+ end
end
-
end
end
end
diff --git a/lib/chef/audit/logger.rb b/lib/chef/knife/user_invite_list.rb
index 759683ccc8..831774d1bf 100644
--- a/lib/chef/audit/logger.rb
+++ b/lib/chef/knife/user_invite_list.rb
@@ -1,5 +1,6 @@
#
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,21 +16,18 @@
# limitations under the License.
#
-require "stringio"
+require_relative "../knife"
class Chef
- class Audit
- class Logger
- def self.puts(message = "")
- @buffer ||= StringIO.new
- @buffer.puts(message)
+ class Knife
+ class UserInviteList < Chef::Knife
+ category "user"
+ banner "knife user invite list"
- Chef::Log.info(message)
- end
-
- def self.read_buffer
- return "" if @buffer.nil?
- @buffer.string
+ def run
+ api_endpoint = "association_requests/"
+ invited_users = rest.get_rest(api_endpoint).map { |i| i["username"] }
+ ui.output(invited_users)
end
end
end
diff --git a/lib/chef/knife/user_invite_rescind.rb b/lib/chef/knife/user_invite_rescind.rb
new file mode 100644
index 0000000000..fd5804e10a
--- /dev/null
+++ b/lib/chef/knife/user_invite_rescind.rb
@@ -0,0 +1,63 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../knife"
+
+class Chef
+ class Knife
+ class UserInviteRescind < Chef::Knife
+ category "user"
+ banner "knife user invite rescind [USERNAMES] (options)"
+
+ option :all,
+ short: "-a",
+ long: "--all",
+ description: "Rescind all invites!"
+
+ def run
+ if (name_args.length < 1) && ! config.key?(:all)
+ show_usage
+ ui.fatal("You must specify a username.")
+ exit 1
+ end
+
+ # To rescind we need to send a DELETE to association_requests/INVITE_ID
+ # For user friendliness we look up the invite ID based on username.
+ @invites = {}
+ usernames = name_args
+ rest.get_rest("association_requests").each { |i| @invites[i["username"]] = i["id"] }
+ if config[:all]
+ ui.confirm("Are you sure you want to rescind all association requests")
+ @invites.each do |u, i|
+ rest.delete_rest("association_requests/#{i}")
+ end
+ else
+ ui.confirm("Are you sure you want to rescind the association requests for: #{usernames.join(", ")}")
+ usernames.each do |u|
+ if @invites.key?(u)
+ rest.delete_rest("association_requests/#{@invites[u]}")
+ else
+ ui.fatal("No association request for #{u}.")
+ exit 1
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/user_key_create.rb b/lib/chef/knife/user_key_create.rb
index 95a98a2f4f..efc783dd7f 100644
--- a/lib/chef/knife/user_key_create.rb
+++ b/lib/chef/knife/user_key_create.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Cloke (tyler@chef.io)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc
+# Copyright:: Copyright (c) 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/knife"
-require "chef/knife/key_create_base"
+require_relative "../knife"
+require_relative "key_create_base"
class Chef
class Knife
@@ -32,6 +32,10 @@ class Chef
banner "knife user key create USER (options)"
+ deps do
+ require_relative "key_create"
+ end
+
attr_reader :actor
def initialize(argv = [])
diff --git a/lib/chef/knife/user_key_delete.rb b/lib/chef/knife/user_key_delete.rb
index 1c559f2ef0..b4f84fdb7b 100644
--- a/lib/chef/knife/user_key_delete.rb
+++ b/lib/chef/knife/user_key_delete.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Cloke (tyler@chef.io)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc
+# Copyright:: Copyright (c) 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"
+require_relative "../knife"
class Chef
class Knife
@@ -29,6 +29,10 @@ class Chef
class UserKeyDelete < Knife
banner "knife user key delete USER KEYNAME (options)"
+ deps do
+ require_relative "key_delete"
+ end
+
attr_reader :actor
def initialize(argv = [])
diff --git a/lib/chef/knife/user_key_edit.rb b/lib/chef/knife/user_key_edit.rb
index 561af8edd0..15ef2ada1e 100644
--- a/lib/chef/knife/user_key_edit.rb
+++ b/lib/chef/knife/user_key_edit.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Cloke (tyler@chef.io)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc
+# Copyright:: Copyright (c) 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/knife"
-require "chef/knife/key_edit_base"
+require_relative "../knife"
+require_relative "key_edit_base"
class Chef
class Knife
@@ -32,6 +32,10 @@ class Chef
banner "knife user key edit USER KEYNAME (options)"
+ deps do
+ require_relative "key_edit"
+ end
+
attr_reader :actor
def initialize(argv = [])
diff --git a/lib/chef/knife/user_key_list.rb b/lib/chef/knife/user_key_list.rb
index 799c069182..781998b301 100644
--- a/lib/chef/knife/user_key_list.rb
+++ b/lib/chef/knife/user_key_list.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Cloke (tyler@chef.io)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc
+# Copyright:: Copyright (c) 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/knife"
-require "chef/knife/key_list_base"
+require_relative "../knife"
+require_relative "key_list_base"
class Chef
class Knife
@@ -32,6 +32,10 @@ class Chef
banner "knife user key list USER (options)"
+ deps do
+ require_relative "key_list"
+ end
+
attr_reader :actor
def initialize(argv = [])
diff --git a/lib/chef/knife/user_key_show.rb b/lib/chef/knife/user_key_show.rb
index e09d5e04ed..2bf535c792 100644
--- a/lib/chef/knife/user_key_show.rb
+++ b/lib/chef/knife/user_key_show.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Cloke (tyler@chef.io)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc
+# Copyright:: Copyright (c) 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"
+require_relative "../knife"
class Chef
class Knife
@@ -29,6 +29,10 @@ class Chef
class UserKeyShow < Knife
banner "knife user key show USER KEYNAME (options)"
+ deps do
+ require_relative "key_show"
+ end
+
attr_reader :actor
def initialize(argv = [])
diff --git a/lib/chef/knife/user_list.rb b/lib/chef/knife/user_list.rb
index 88e834025d..f6aa7bcfc4 100644
--- a/lib/chef/knife/user_list.rb
+++ b/lib/chef/knife/user_list.rb
@@ -1,6 +1,6 @@
#
# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,25 +16,22 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../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_v1"
- require "chef/json_compat"
+ require_relative "../user_v1"
end
banner "knife user list (options)"
option :with_uri,
- :short => "-w",
- :long => "--with-uri",
- :description => "Show corresponding URIs"
+ short: "-w",
+ long: "--with-uri",
+ description: "Show corresponding URIs."
def run
output(format_list_for_display(Chef::UserV1.list))
diff --git a/lib/chef/knife/user_reregister.rb b/lib/chef/knife/user_reregister.rb
index 8d2f2c1e73..ee58c19d9f 100644
--- a/lib/chef/knife/user_reregister.rb
+++ b/lib/chef/knife/user_reregister.rb
@@ -1,6 +1,6 @@
#
# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,41 +16,22 @@
# limitations under the License.
#
-require "chef/knife"
+require_relative "../knife"
class Chef
class Knife
class UserReregister < Knife
deps do
- require "chef/user_v1"
- require "chef/json_compat"
+ require_relative "../user_v1"
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",
- :description => "Write the private key to a file"
+ short: "-f FILE",
+ long: "--file FILE",
+ description: "Write the private key to a file."
def run
@user_name = @name_args[0]
@@ -62,26 +43,15 @@ EOF
end
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
+ user.reregister
+ Chef::Log.trace("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
diff --git a/lib/chef/knife/user_show.rb b/lib/chef/knife/user_show.rb
index 04251c0863..e59f969e9a 100644
--- a/lib/chef/knife/user_show.rb
+++ b/lib/chef/knife/user_show.rb
@@ -1,6 +1,6 @@
#
# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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"
+require_relative "../knife"
class Chef
class Knife
@@ -25,30 +25,11 @@ class Chef
include Knife::Core::MultiAttributeReturnOption
deps do
- require "chef/user_v1"
- require "chef/json_compat"
+ require_relative "../user_v1"
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]
@@ -59,18 +40,7 @@ EOF
end
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
+ output(format_for_display(user))
end
end
diff --git a/lib/chef/knife/xargs.rb b/lib/chef/knife/xargs.rb
index 6559ca2e74..9dcc724d38 100644
--- a/lib/chef/knife/xargs.rb
+++ b/lib/chef/knife/xargs.rb
@@ -1,74 +1,90 @@
-require "chef/chef_fs/knife"
+#
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../chef_fs/knife"
class Chef
class Knife
class Xargs < Chef::ChefFS::Knife
- banner "knife xargs [COMMAND]"
+ banner "knife xargs [COMMAND] (options)"
category "path-based"
deps do
- require "chef/chef_fs/file_system"
- require "chef/chef_fs/file_system/not_found_error"
+ require_relative "../chef_fs/file_system"
+ require_relative "../chef_fs/file_system/exceptions"
end
# TODO modify to remote-only / local-only pattern (more like delete)
option :local,
- :long => "--local",
- :boolean => true,
- :description => "Xargs local files instead of remote"
+ long: "--local",
+ boolean: true,
+ description: "Xargs local files instead of remote."
option :patterns,
- :long => "--pattern [PATTERN]",
- :short => "-p [PATTERN]",
- :description => "Pattern on command line (if these are not specified, a list of patterns is expected on standard input). Multiple patterns may be passed in this way.",
- :arg_arity => [1, -1]
+ long: "--pattern [PATTERN]",
+ short: "-p [PATTERN]",
+ description: "Pattern on command line (if these are not specified, a list of patterns is expected on standard input). Multiple patterns may be passed in this way.",
+ arg_arity: [1, -1]
option :diff,
- :long => "--[no-]diff",
- :default => true,
- :boolean => true,
- :description => "Whether to show a diff when files change (default: true)"
+ long: "--[no-]diff",
+ default: true,
+ boolean: true,
+ description: "Whether to show a diff when files change (default: true)."
option :dry_run,
- :long => "--dry-run",
- :boolean => true,
- :description => "Prevents changes from actually being uploaded to the server."
+ long: "--dry-run",
+ boolean: true,
+ description: "Prevents changes from actually being uploaded to the server."
option :force,
- :long => "--[no-]force",
- :boolean => true,
- :default => false,
- :description => "Force upload of files even if they are not changed (quicker and harmless, but doesn't print out what it changed)"
+ long: "--[no-]force",
+ boolean: true,
+ default: false,
+ description: "Force upload of files even if they are not changed (quicker and harmless, but doesn't print out what it changed)."
option :replace_first,
- :long => "--replace-first REPLACESTR",
- :short => "-J REPLACESTR",
- :description => "String to replace with filenames. -J will only replace the FIRST occurrence of the replacement string."
+ long: "--replace-first REPLACESTR",
+ short: "-J REPLACESTR",
+ description: "String to replace with filenames. -J will only replace the FIRST occurrence of the replacement string."
option :replace_all,
- :long => "--replace REPLACESTR",
- :short => "-I REPLACESTR",
- :description => "String to replace with filenames. -I will replace ALL occurrence of the replacement string."
+ long: "--replace REPLACESTR",
+ short: "-I REPLACESTR",
+ description: "String to replace with filenames. -I will replace ALL occurrence of the replacement string."
option :max_arguments_per_command,
- :long => "--max-args MAXARGS",
- :short => "-n MAXARGS",
- :description => "Maximum number of arguments per command line."
+ long: "--max-args MAXARGS",
+ short: "-n MAXARGS",
+ description: "Maximum number of arguments per command line."
option :max_command_line,
- :long => "--max-chars LENGTH",
- :short => "-s LENGTH",
- :description => "Maximum size of command line, in characters"
+ long: "--max-chars LENGTH",
+ short: "-s LENGTH",
+ description: "Maximum size of command line, in characters."
option :verbose_commands,
- :short => "-t",
- :description => "Print command to be run on the command line"
+ short: "-t",
+ description: "Print command to be run on the command line."
option :null_separator,
- :short => "-0",
- :boolean => true,
- :description => "Use the NULL character (\0) as a separator, instead of whitespace"
+ short: "-0",
+ boolean: true,
+ description: "Use the NULL character (\0) as a separator, instead of whitespace."
def run
error = false
@@ -159,7 +175,7 @@ class Chef
# Create the temporary files
files.each do |file|
tempfile = Tempfile.new(file.name)
- tempfiles[tempfile] = { :file => file }
+ tempfiles[tempfile] = { file: file }
end
rescue
destroy_tempfiles(files)
@@ -167,7 +183,7 @@ class Chef
end
# Create the command
- paths = tempfiles.keys.map { |tempfile| tempfile.path }.join(" ")
+ paths = tempfiles.keys.map(&:path).join(" ")
if config[:replace_all]
final_command = command.gsub(config[:replace_all], paths)
elsif config[:replace_first]
@@ -181,32 +197,32 @@ class Chef
def destroy_tempfiles(tempfiles)
# Unlink the files now that we're done with them
- tempfiles.keys.each { |tempfile| tempfile.close! }
+ tempfiles.each_key(&:close!)
end
def xargs_files(command, tempfiles)
error = false
# Create the temporary files
tempfiles.each_pair do |tempfile, file|
- begin
- value = file[:file].read
- file[:value] = value
- tempfile.open
- tempfile.write(value)
- tempfile.close
- rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e
- ui.error "#{format_path(e.entry)}: #{e.reason}."
- error = true
- tempfile.close!
- tempfiles.delete(tempfile)
- next
- rescue Chef::ChefFS::FileSystem::NotFoundError => e
- ui.error "#{format_path(e.entry)}: No such file or directory"
- error = true
- tempfile.close!
- tempfiles.delete(tempfile)
- next
- end
+
+ value = file[:file].read
+ file[:value] = value
+ tempfile.open
+ tempfile.write(value)
+ tempfile.close
+ rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e
+ ui.error "#{format_path(e.entry)}: #{e.reason}."
+ error = true
+ tempfile.close!
+ tempfiles.delete(tempfile)
+ next
+ rescue Chef::ChefFS::FileSystem::NotFoundError => e
+ ui.error "#{format_path(e.entry)}: No such file or directory"
+ error = true
+ tempfile.close!
+ tempfiles.delete(tempfile)
+ next
+
end
return error if error && tempfiles.size == 0
diff --git a/lib/chef/knife/yaml_convert.rb b/lib/chef/knife/yaml_convert.rb
new file mode 100644
index 0000000000..6bd2d1c0ea
--- /dev/null
+++ b/lib/chef/knife/yaml_convert.rb
@@ -0,0 +1,91 @@
+#
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Copyright:: Copyright (c) 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.
+#
+
+autoload :YAML, "yaml"
+require_relative "../knife"
+class Chef::Knife::YamlConvert < Chef::Knife
+
+ banner "knife yaml convert YAML_FILENAME [RUBY_FILENAME]"
+
+ def run
+ if name_args.empty?
+ ui.fatal!("Please specify the file name of a YAML recipe to convert to Ruby")
+ elsif name_args.size >= 3
+ ui.fatal!("knife yaml convert YAML_FILENAME [RUBY_FILENAME]")
+ end
+
+ yaml_file = @name_args[0]
+ unless ::File.exist?(yaml_file) && ::File.readable?(yaml_file)
+ ui.fatal("Input YAML file '#{yaml_file}' does not exist or is unreadable")
+ end
+
+ ruby_file = if @name_args[1]
+ @name_args[1] # use the specified output filename if provided
+ else
+ if ::File.extname(yaml_file) == ".yml" || ::File.extname(yaml_file) == ".yaml"
+ yaml_file.gsub(/\.(yml|yaml)$/, ".rb")
+ else
+ yaml_file + ".rb" # fall back to putting .rb on the end of whatever the yaml file was named
+ end
+ end
+
+ if ::File.exist?(ruby_file)
+ ui.fatal!("Output Ruby file '#{ruby_file}' already exists")
+ end
+
+ yaml_contents = IO.read(yaml_file)
+
+ # YAML can contain multiple documents (--- is the separator), let's not support that.
+ if ::YAML.load_stream(yaml_contents).length > 1
+ ui.fatal!("YAML recipe '#{yaml_file}' contains multiple documents, only one is supported")
+ end
+
+ # Unfortunately, per the YAML spec, comments are stripped when we load, so we lose them on conversion
+ yaml_hash = ::YAML.safe_load(yaml_contents, permitted_classes: [Symbol])
+ unless yaml_hash.is_a?(Hash) && yaml_hash.key?("resources")
+ ui.fatal!("YAML recipe '#{source_file}' must contain a top-level 'resources' hash (YAML sequence), i.e. 'resources:'")
+ end
+
+ ui.warn("No resources found in '#{yaml_file}'") if yaml_hash["resources"].size == 0
+
+ ::File.open(ruby_file, "w") do |file|
+ file.write(resource_hash_to_string(yaml_hash["resources"], yaml_file))
+ end
+ ui.info("Converted '#{yaml_file}' to '#{ruby_file}'")
+ end
+
+ # Converts a Hash of resources to a Ruby recipe
+ # returns a string ready to be written to a file or stdout
+ def resource_hash_to_string(resource_hash, filename)
+ ruby_contents = []
+ ruby_contents << "# Autoconverted recipe from #{filename}\n"
+
+ resource_hash.each do |r|
+ type = r.delete("type")
+ name = r.delete("name")
+
+ ruby_contents << "#{type} \"#{name}\" do"
+ r.each do |k, v|
+ ruby_contents << " #{k} #{v.inspect}"
+ end
+ ruby_contents << "end\n"
+ end
+
+ ruby_contents.join("\n")
+ end
+end
diff --git a/lib/chef/local_mode.rb b/lib/chef/local_mode.rb
index 5ce17e6fb3..e7346322d2 100644
--- a/lib/chef/local_mode.rb
+++ b/lib/chef/local_mode.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,12 +14,10 @@
# WITHOUT WARRANTIES OR 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"
-if Chef::Platform.windows?
- if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.1")
- require "chef/monkey_patches/webrick-utils"
- end
-end
+
+require "chef-utils" unless defined?(ChefUtils::CANARY)
+require_relative "config"
+require_relative "monkey_patches/webrick-utils" if ChefUtils.windows?
class Chef
module LocalMode
@@ -55,8 +53,8 @@ class Chef
destroy_server_connectivity
require "chef_zero/server"
- require "chef/chef_fs/chef_fs_data_store"
- require "chef/chef_fs/config"
+ require_relative "chef_fs/chef_fs_data_store"
+ require_relative "chef_fs/config"
@chef_fs = Chef::ChefFS::Config.new.local_fs
@chef_fs.write_pretty_json = true
@@ -73,6 +71,7 @@ class Chef
@chef_zero_server = ChefZero::Server.new(server_options)
if Chef::Config[:listen]
+ Chef.deprecated(:local_listen, "Starting local-mode server in deprecated socket mode")
@chef_zero_server.start_background
else
@chef_zero_server.start_socketless
@@ -80,7 +79,7 @@ class Chef
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::Log.info("Started #{ChefUtils::Dist::Zero::PRODUCT} at #{local_mode_url} with #{@chef_fs.fs_description}")
Chef::Config.chef_server_url = local_mode_url
end
end
diff --git a/lib/chef/log.rb b/lib/chef/log.rb
index ac2baeb9d1..8cf2fba9bd 100644
--- a/lib/chef/log.rb
+++ b/lib/chef/log.rb
@@ -2,7 +2,7 @@
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: AJ Christensen (<@aj@opscode.com>)
# Author:: Christopher Brown (<cb@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,18 +18,23 @@
# limitations under the License.
require "logger"
-require "chef/monologger"
-require "chef/exceptions"
+require_relative "monologger"
+require_relative "exceptions"
require "mixlib/log"
-require "chef/log/syslog" unless RUBY_PLATFORM =~ /mswin|mingw|windows/
-require "chef/log/winevt"
+require_relative "log/syslog" unless RUBY_PLATFORM.match?(/mswin|mingw|windows/)
+require_relative "log/winevt"
class Chef
class Log
extend Mixlib::Log
+ def self.setup!
+ init(MonoLogger.new(STDOUT))
+ nil
+ end
+
# Force initialization of the primary log device (@logger)
- init(MonoLogger.new(STDOUT))
+ setup!
class Formatter
def self.show_time=(*args)
@@ -46,16 +51,18 @@ class Chef
#
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).find { |c| !c.start_with?(chef_gem_path) }
+ # thing the user wrote. Or failing that, the most recent caller.
+ chef_gem_path = File.expand_path("..", __dir__)
+ caller(0..20).find { |c| !c.start_with?(chef_gem_path) } || caller(0..1)[0]
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
+ # Log a deprecation warning.
+ #
+ # If the treat_deprecation_warnings_as_errors config option is set, this
+ # will raise an exception instead.
+ #
+ # @param msg [String] Deprecation message to display.
+ def self.deprecation(msg, &block)
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
index 58d6671095..4e6a6dd0b5 100644
--- a/lib/chef/log/syslog.rb
+++ b/lib/chef/log/syslog.rb
@@ -1,7 +1,7 @@
#
# Author:: Lamont Granquist (<lamont@chef.io>)
# Author:: SAWANOBORI Yukihiko (<sawanoboriyu@higanworks.com>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,7 +18,8 @@
require "logger"
require "syslog-logger"
-require "chef/mixin/unformatter"
+require_relative "../mixin/unformatter"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Log
@@ -32,14 +33,14 @@ class Chef
attr_accessor :sync, :formatter
- def initialize(program_name = "chef-client", facility = ::Syslog::LOG_DAEMON, logopts = nil)
+ def initialize(program_name = ChefUtils::Dist::Infra::CLIENT, facility = ::Syslog::LOG_DAEMON, logopts = nil)
super
return if defined? ::Logger::Syslog::SYSLOG
+
::Logger::Syslog.const_set :SYSLOG, SYSLOG
end
- def close
- end
+ def close; end
end
end
end
diff --git a/lib/chef/log/winevt.rb b/lib/chef/log/winevt.rb
index 506d4c9a6c..f060ecfde6 100644
--- a/lib/chef/log/winevt.rb
+++ b/lib/chef/log/winevt.rb
@@ -1,7 +1,7 @@
#
# Author:: Jay Mundrawala (<jdm@chef.io>)
#
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,9 +16,10 @@
# limitations under the License.
#
-require "chef/event_loggers/base"
-require "chef/platform/query_helpers"
-require "chef/mixin/unformatter"
+require_relative "../event_loggers/base"
+require_relative "../platform/query_helpers"
+require_relative "../mixin/unformatter"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Log
@@ -36,7 +37,7 @@ class Chef
FATAL_EVENT_ID = 10104
# Since we must install the event logger, this is not really configurable
- SOURCE = "Chef"
+ SOURCE = ChefUtils::Dist::Infra::SHORT.freeze
include Chef::Mixin::Unformatter
@@ -46,51 +47,50 @@ class Chef
@eventlog = eventlog || ::Win32::EventLog.open("Application")
end
- def close
- 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]
+ 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]
+ 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]
+ 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]
+ 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]
+ event_type: ::Win32::EventLog::ERROR_TYPE,
+ source: SOURCE,
+ event_id: FATAL_EVENT_ID,
+ data: [msg]
)
end
diff --git a/lib/chef/mash.rb b/lib/chef/mash.rb
index 3858ff09dd..d386af2591 100644
--- a/lib/chef/mash.rb
+++ b/lib/chef/mash.rb
@@ -1,226 +1,21 @@
-# Copyright 2009-2016, Dan Kubb
-
-# 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.
-
-# ---
-# ---
-
-# Some portions of blank.rb and mash.rb are verbatim copies of software
-# licensed under the MIT license. That license is included below:
-
-# Copyright 2005-2016, David Heinemeier Hansson
-
-# 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.
-
-# This class has dubious semantics and we only have it so that people can write
-# params[:key] instead of params['key'].
-class Mash < Hash
-
- # @param constructor<Object>
- # The default value for the mash. Defaults to an empty hash.
- #
- # @details [Alternatives]
- # If constructor is a Hash, a new mash will be created based on the keys of
- # the hash and no default value will be set.
- def initialize(constructor = {})
- if constructor.is_a?(Hash)
- super()
- update(constructor)
- else
- super(constructor)
- end
- end
-
- # @param orig<Object> Mash being copied
- #
- # @return [Object] A new copied Mash
- def initialize_copy(orig)
- super
- # Handle nested values
- each do |k, v|
- if v.kind_of?(Mash) || v.is_a?(Array)
- self[k] = v.dup
- end
- end
- self
- end
-
- # @param key<Object> The default value for the mash. Defaults to nil.
- #
- # @details [Alternatives]
- # If key is a Symbol and it is a key in the mash, then the default value will
- # be set to the value matching the key.
- def default(key = nil)
- if key.is_a?(Symbol) && include?(key = key.to_s)
- self[key]
- else
- super
- end
- end
-
- alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
- alias_method :regular_update, :update unless method_defined?(:regular_update)
-
- # @param key<Object> The key to set.
- # @param value<Object>
- # The value to set the key to.
- #
- # @see Mash#convert_key
- # @see Mash#convert_value
- def []=(key, value)
- regular_writer(convert_key(key), convert_value(value))
- end
-
- # @param other_hash<Hash>
- # A hash to update values in the mash with. The keys and the values will be
- # converted to Mash format.
- #
- # @return [Mash] The updated mash.
- def update(other_hash)
- other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
- self
- end
-
- alias_method :merge!, :update
-
- # @param key<Object> The key to check for. This will be run through convert_key.
- #
- # @return [Boolean] True if the key exists in the mash.
- def key?(key)
- super(convert_key(key))
- end
-
- # def include? def has_key? def member?
- alias_method :include?, :key?
- alias_method :has_key?, :key?
- alias_method :member?, :key?
-
- # @param key<Object> The key to fetch. This will be run through convert_key.
- # @param *extras<Array> Default value.
- #
- # @return [Object] The value at key or the default value.
- def fetch(key, *extras)
- super(convert_key(key), *extras)
- end
-
- # @param *indices<Array>
- # The keys to retrieve values for. These will be run through +convert_key+.
- #
- # @return [Array] The values at each of the provided keys
- def values_at(*indices)
- indices.collect { |key| self[convert_key(key)] }
- end
-
- # @param hash<Hash> The hash to merge with the mash.
- #
- # @return [Mash] A new mash with the hash values merged in.
- def merge(hash)
- self.dup.update(hash)
- end
-
- # @param key<Object>
- # The key to delete from the mash.\
- def delete(key)
- super(convert_key(key))
- end
-
- # @param *rejected<Array[(String, Symbol)] The mash keys to exclude.
- #
- # @return [Mash] A new mash without the selected keys.
- #
- # @example
- # { :one => 1, :two => 2, :three => 3 }.except(:one)
- # #=> { "two" => 2, "three" => 3 }
- def except(*keys)
- super(*keys.map { |k| convert_key(k) })
- end
-
- # Used to provide the same interface as Hash.
- #
- # @return [Mash] This mash unchanged.
- def stringify_keys!; self end
-
- # @return [Hash] The mash as a Hash with symbolized keys.
- def symbolize_keys
- h = Hash.new(default)
- each { |key, val| h[key.to_sym] = val }
- h
- end
-
- # @return [Hash] The mash as a Hash with string keys.
- def to_hash
- Hash.new(default).merge(self)
- end
-
- # @return [Mash] Convert a Hash into a Mash
- # The input Hash's default value is maintained
- def self.from_hash(hash)
- mash = Mash.new(hash)
- mash.default = hash.default
- mash
- end
-
- protected
-
- # @param key<Object> The key to convert.
- #
- # @param [Object]
- # The converted key. If the key was a symbol, it will be converted to a
- # string.
- #
- # @api private
- def convert_key(key)
- key.kind_of?(Symbol) ? key.to_s : key
- end
-
- # @param value<Object> The value to convert.
- #
- # @return [Object]
- # The converted value. A Hash or an Array of hashes, will be converted to
- # their Mash equivalents.
- #
- # @api private
- def convert_value(value)
- if value.class == Hash
- Mash.from_hash(value)
- elsif value.is_a?(Array)
- value.collect { |e| convert_value(e) }
- else
- value
- end
- end
-end
+#
+# Copyright:: Copyright (c) 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-utils/mash" unless defined?(ChefUtils::Mash)
+
+# For historical reasons we inject Mash directly into the top level class namespace
+Mash = ChefUtils::Mash unless defined?(Mash)
diff --git a/lib/chef/mixin/api_version_request_handling.rb b/lib/chef/mixin/api_version_request_handling.rb
index b91a1dfe0a..a46e001dd8 100644
--- a/lib/chef/mixin/api_version_request_handling.rb
+++ b/lib/chef/mixin/api_version_request_handling.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Cloke (tyler@chef.io)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,25 +19,23 @@
class Chef
module Mixin
module ApiVersionRequestHandling
- # Input:
- # exeception:
- # Net::HTTPServerException that may or may not contain the x-ops-server-api-version header
+ # @param exception [Net::HTTPClientException] 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.
+ # @param supported_client_versions [Array<Integer>] The API versions the client supports.
#
# Output:
# nil:
- # If the execption was not a 406 or the server does not support versioning
+ # If the exception 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:
+ # Array 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
+ server_supported_client_versions = []
header = Chef::JSONCompat.from_json(exception.response["x-ops-server-api-version"])
min_server_version = Integer(header["min_version"])
@@ -52,13 +50,13 @@ class Chef
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
+ <<~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 commands.
+ Please refer to the documentation on how to manage your keys via the key rotation commands:
+ https://docs.chef.io/ctl_chef_server/#key-rotation
+ EOH
end
end
diff --git a/lib/chef/mixin/checksum.rb b/lib/chef/mixin/checksum.rb
index f223894c39..083e524d63 100644
--- a/lib/chef/mixin/checksum.rb
+++ b/lib/chef/mixin/checksum.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,8 +16,7 @@
# limitations under the License.
#
-require "digest/sha2"
-require "chef/digester"
+require_relative "../digester"
class Chef
module Mixin
@@ -27,6 +26,11 @@ class Chef
Chef::Digester.checksum_for_file(file)
end
+ def short_cksum(checksum)
+ return "none" if checksum.nil?
+
+ checksum.slice(0, 6)
+ end
end
end
end
diff --git a/lib/chef/mixin/language_include_recipe.rb b/lib/chef/mixin/chef_utils_wiring.rb
index 97e384c7c4..938520059c 100644
--- a/lib/chef/mixin/language_include_recipe.rb
+++ b/lib/chef/mixin/chef_utils_wiring.rb
@@ -1,6 +1,5 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+#--
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,18 +13,28 @@
# WITHOUT WARRANTIES OR 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/include_recipe"
-require "chef/mixin/deprecation"
+require_relative "../log"
+require_relative "../config"
+require_relative "../chef_class"
class Chef
module Mixin
+ # Common Dependency Injection wiring for ChefUtils-related modules
+ module ChefUtilsWiring
+ private
+
+ def __config
+ Chef::Config
+ end
- deprecate_constant(:LanguageIncludeRecipe, Chef::DSL::IncludeRecipe, <<-EOM)
-Chef::Mixin::LanguageIncludeRecipe is deprecated, use Chef::DSL::IncludeRecipe
-instead.
-EOM
+ def __log
+ Chef::Log
+ end
+ def __transport_connection
+ Chef.run_context&.transport_connection
+ end
+ end
end
end
diff --git a/lib/chef/mixin/command.rb b/lib/chef/mixin/command.rb
deleted file mode 100644
index 0cc3143ec7..0000000000
--- a/lib/chef/mixin/command.rb
+++ /dev/null
@@ -1,193 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-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/log"
-require "chef/exceptions"
-require "tmpdir"
-require "fcntl"
-require "etc"
-
-class Chef
- module Mixin
-
- #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- # NOTE:
- # The popen4 method upon which all the code here is based has a race
- # condition where it may fail to read all of the data written to stdout and
- # stderr after the child process exits. The tests for the code here
- # occasionally fail because of this race condition, so they have been
- # tagged "volatile".
- #
- # This code is considered deprecated, so it should not need to be modified
- # frequently, if at all. HOWEVER, if you do modify the code here, you must
- # explicitly enable volatile tests:
- #
- # bundle exec rspec spec/unit/mixin/command_spec.rb -t volatile
- #
- # In addition, you should make a note that tests need to be run with
- # volatile tests enabled on any pull request or bug report you submit with
- # your patch.
- #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-
- module Command
- extend self
-
- # 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"
- include ::Chef::Mixin::Command::Windows
- extend ::Chef::Mixin::Command::Windows
- else
- require "chef/mixin/command/unix"
- include ::Chef::Mixin::Command::Unix
- extend ::Chef::Mixin::Command::Unix
- end
-
- # === Parameters
- # args<Hash>: A number of required and optional arguments
- # command<String>, <Array>: A complete command with options to execute or a command and options as an Array
- # creates<String>: The absolute path to a file that prevents the command from running if it exists
- # cwd<String>: Working directory to execute command in, defaults to Dir.tmpdir
- # timeout<String>: How many seconds to wait for the command to execute before timing out
- # returns<String>: The single exit value command is expected to return, otherwise causes an exception
- # ignore_failure<Boolean>: Whether to raise an exception on failure, or just return the status
- # output_on_failure<Boolean>: Return output in raised exception regardless of Log.level
- #
- # user<String>: The UID or user name of the user to execute the command as
- # group<String>: The GID or group name of the group to execute the command as
- # environment<Hash>: Pairs of environment variable names and their values to set before execution
- #
- # === Returns
- # Returns the exit status of args[:command]
- def run_command(args = {})
- status, stdout, stderr = run_command_and_return_stdout_stderr(args)
-
- status
- end
-
- # works same as above, except that it returns stdout and stderr
- # requirement => platforms like solaris 9,10 has weird issues where
- # even in command failure the exit code is zero, so we need to lookup stderr.
- def run_command_and_return_stdout_stderr(args = {})
- command_output = ""
-
- args[:ignore_failure] ||= false
- args[:output_on_failure] ||= false
-
- # TODO: This is the wrong place for this responsibility.
- if args.has_key?(:creates)
- if File.exists?(args[:creates])
- Chef::Log.debug("Skipping #{args[:command]} - creates #{args[:creates]} exists.")
- return false
- end
- end
-
- status, stdout, stderr = output_of_command(args[:command], args)
- command_output << "STDOUT: #{stdout}"
- command_output << "STDERR: #{stderr}"
- handle_command_failures(status, command_output, args)
-
- return status, stdout, stderr
- end
-
- def output_of_command(command, args)
- Chef::Log.debug("Executing #{command}")
- stderr_string, stdout_string, status = "", "", nil
-
- exec_processing_block = lambda do |pid, stdin, stdout, stderr|
- stdout_string, stderr_string = stdout.string.chomp, stderr.string.chomp
- end
-
- args[:cwd] ||= Dir.tmpdir
- unless ::File.directory?(args[:cwd])
- raise Chef::Exceptions::Exec, "#{args[:cwd]} does not exist or is not a directory"
- end
-
- Dir.chdir(args[:cwd]) do
- if args[:timeout]
- begin
- Timeout.timeout(args[:timeout]) do
- status = popen4(command, args, &exec_processing_block)
- end
- rescue Timeout::Error => e
- Chef::Log.error("#{command} exceeded timeout #{args[:timeout]}")
- raise(e)
- end
- else
- status = popen4(command, args, &exec_processing_block)
- end
-
- Chef::Log.debug("---- Begin output of #{command} ----")
- Chef::Log.debug("STDOUT: #{stdout_string}")
- Chef::Log.debug("STDERR: #{stderr_string}")
- Chef::Log.debug("---- End output of #{command} ----")
- Chef::Log.debug("Ran #{command} returned #{status.exitstatus}")
- end
-
- return status, stdout_string, stderr_string
- end
-
- def handle_command_failures(status, command_output, opts = {})
- return if opts[:ignore_failure]
- opts[:returns] ||= 0
- return if Array(opts[:returns]).include?(status.exitstatus)
-
- # if the log level is not debug, through output of command when we fail
- output = ""
- if Chef::Log.level == :debug || opts[:output_on_failure]
- output << "\n---- Begin output of #{opts[:command]} ----\n"
- output << command_output.to_s
- output << "\n---- End output of #{opts[:command]} ----\n"
- end
- raise Chef::Exceptions::Exec, "#{opts[:command]} returned #{status.exitstatus}, expected #{opts[:returns]}#{output}"
- end
-
- # Call #run_command but set LC_ALL to the system's current environment so it doesn't get changed to C.
- #
- # === Parameters
- # args<Hash>: A number of required and optional arguments that will be handed out to #run_command
- #
- # === Returns
- # Returns the result of #run_command
- def run_command_with_systems_locale(args = {})
- args[:environment] ||= {}
- args[:environment]["LC_ALL"] = ENV["LC_ALL"]
- run_command args
- end
-
- # def popen4(cmd, args={}, &b)
- # @@os_handler.popen4(cmd, args, &b)
- # end
-
- # module_function :popen4
-
- # FIXME: yard with @yield
- def chdir_or_tmpdir(dir)
- dir ||= Dir.tmpdir
- unless File.directory?(dir)
- raise Chef::Exceptions::Exec, "#{dir} does not exist or is not a directory"
- end
- Dir.chdir(dir) do
- yield
- end
- end
-
- end
- end
-end
diff --git a/lib/chef/mixin/command/unix.rb b/lib/chef/mixin/command/unix.rb
deleted file mode 100644
index aa541c3637..0000000000
--- a/lib/chef/mixin/command/unix.rb
+++ /dev/null
@@ -1,220 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-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.
-#
-
-class Chef
- module Mixin
- module Command
- module Unix
- # This is taken directly from Ara T Howard's Open4 library, and then
- # modified to suit the needs of Chef. Any bugs here are most likely
- # my own, and not Ara's.
- #
- # The original appears in external/open4.rb in its unmodified form.
- #
- # Thanks Ara!
- def popen4(cmd, args = {}, &b)
- # Ruby 1.8 suffers from intermittent segfaults believed to be due to GC while IO.select
- # See CHEF-2916 / CHEF-1305
- GC.disable
-
- # Waitlast - this is magic.
- #
- # Do we wait for the child process to die before we yield
- # to the block, or after? That is the magic of waitlast.
- #
- # By default, we are waiting before we yield the block.
- args[:waitlast] ||= false
-
- args[:user] ||= nil
- unless args[:user].kind_of?(Integer)
- args[:user] = Etc.getpwnam(args[:user]).uid if args[:user]
- end
- args[:group] ||= nil
- unless args[:group].kind_of?(Integer)
- args[:group] = Etc.getgrnam(args[:group]).gid if args[:group]
- end
- args[:environment] ||= {}
-
- # Default on C locale so parsing commands output can be done
- # independently of the node's default locale.
- # "LC_ALL" could be set to nil, in which case we also must ignore it.
- unless args[:environment].has_key?("LC_ALL")
- args[:environment]["LC_ALL"] = "C"
- end
-
- pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
-
- verbose = $VERBOSE
- begin
- $VERBOSE = nil
- ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
-
- cid = fork do
- pw.last.close
- STDIN.reopen pw.first
- pw.first.close
-
- pr.first.close
- STDOUT.reopen pr.last
- pr.last.close
-
- pe.first.close
- STDERR.reopen pe.last
- pe.last.close
-
- STDOUT.sync = STDERR.sync = true
-
- if args[:group]
- Process.egid = args[:group]
- Process.gid = args[:group]
- end
-
- if args[:user]
- Process.euid = args[:user]
- Process.uid = args[:user]
- end
-
- args[:environment].each do |key, value|
- ENV[key] = value
- end
-
- if args[:umask]
- umask = ((args[:umask].respond_to?(:oct) ? args[:umask].oct : args[:umask].to_i) & 007777)
- File.umask(umask)
- end
-
- begin
- if cmd.kind_of?(Array)
- Kernel.exec(*cmd)
- else
- Kernel.exec(cmd)
- end
- raise "forty-two"
- rescue Exception => e
- Marshal.dump(e, ps.last)
- ps.last.flush
- end
- ps.last.close unless ps.last.closed?
- exit!
- end
- ensure
- $VERBOSE = verbose
- end
-
- [pw.first, pr.last, pe.last, ps.last].each { |fd| fd.close }
-
- begin
- e = Marshal.load ps.first
- raise(Exception === e ? e : "unknown failure!")
- rescue EOFError # If we get an EOF error, then the exec was successful
- 42
- ensure
- ps.first.close
- end
-
- pw.last.sync = true
-
- pi = [pw.last, pr.first, pe.first]
-
- if b
- begin
- if args[:waitlast]
- b[cid, *pi]
- # send EOF so that if the child process is reading from STDIN
- # it will actually finish up and exit
- pi[0].close_write
- Process.waitpid2(cid).last
- else
- # This took some doing.
- # The trick here is to close STDIN
- # Then set our end of the childs pipes to be O_NONBLOCK
- # Then wait for the child to die, which means any IO it
- # wants to do must be done - it's dead. If it isn't,
- # it's because something totally skanky is happening,
- # and we don't care.
- o = StringIO.new
- e = StringIO.new
-
- pi[0].close
-
- stdout = pi[1]
- stderr = pi[2]
-
- stdout.sync = true
- stderr.sync = true
-
- stdout.fcntl(Fcntl::F_SETFL, pi[1].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
- stderr.fcntl(Fcntl::F_SETFL, pi[2].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
-
- stdout_finished = false
- stderr_finished = false
-
- results = nil
-
- while !stdout_finished || !stderr_finished
- begin
- channels_to_watch = []
- channels_to_watch << stdout if !stdout_finished
- channels_to_watch << stderr if !stderr_finished
- ready = IO.select(channels_to_watch, nil, nil, 1.0)
- rescue Errno::EAGAIN
- ensure
- results = Process.waitpid2(cid, Process::WNOHANG)
- if results
- stdout_finished = true
- stderr_finished = true
- end
- end
-
- if ready && ready.first.include?(stdout)
- line = results ? stdout.gets(nil) : stdout.gets
- if line
- o.write(line)
- else
- stdout_finished = true
- end
- end
- if ready && ready.first.include?(stderr)
- line = results ? stderr.gets(nil) : stderr.gets
- if line
- e.write(line)
- else
- stderr_finished = true
- end
- end
- end
- results = Process.waitpid2(cid) unless results
- o.rewind
- e.rewind
- b[cid, pi[0], o, e]
- results.last
- end
- ensure
- pi.each { |fd| fd.close unless fd.closed? }
- end
- else
- [cid, pw.last, pr.first, pe.first]
- end
- ensure
- GC.enable
- end
-
- end
- end
- end
-end
diff --git a/lib/chef/mixin/command/windows.rb b/lib/chef/mixin/command/windows.rb
deleted file mode 100644
index fd45ab0467..0000000000
--- a/lib/chef/mixin/command/windows.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
-# Author:: Doug MacEachern (<dougm@vmware.com>)
-# Copyright:: Copyright 2010-2016, VMware, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require "open3"
-
-class Chef
- module Mixin
- module Command
- module Windows
- def popen4(cmd, args = {}, &b)
- # By default, we are waiting before we yield the block.
- args[:waitlast] ||= false
-
- #XXX :user, :group, :environment support?
-
- Open3.popen3(cmd) do |stdin, stdout, stderr, cid|
- if b
- if args[:waitlast]
- b[cid, stdin, stdout, stderr]
- # send EOF so that if the child process is reading from STDIN
- # it will actually finish up and exit
- stdin.close_write
- else
- o = StringIO.new
- e = StringIO.new
-
- stdin.close
-
- stdout.sync = true
- stderr.sync = true
-
- line = stdout.gets(nil)
- if line
- o.write(line)
- end
- line = stderr.gets(nil)
- if line
- e.write(line)
- end
- o.rewind
- e.rewind
- b[cid, stdin, o, e]
- end
- else
- [cid, stdin, stdout, stderr]
- end
- end
- $?
- end
-
- end
- end
- end
-end
diff --git a/lib/chef/mixin/convert_to_class_name.rb b/lib/chef/mixin/convert_to_class_name.rb
index d6bd8a4ea7..de11110574 100644
--- a/lib/chef/mixin/convert_to_class_name.rb
+++ b/lib/chef/mixin/convert_to_class_name.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,7 +25,7 @@ class Chef
def convert_to_class_name(str)
str = normalize_snake_case_name(str)
rname = nil
- regexp = %r{^(.+?)(_(.+))?$}
+ regexp = /^(.+?)(_(.+))?$/
mn = str.match(regexp)
if mn
@@ -66,61 +66,6 @@ class Chef
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
- # be deprecated and removed.
- #
- # MIT LICENSE is here: https://github.com/rails/rails/blob/master/activesupport/MIT-LICENSE
-
- # Tries to find a constant with the name specified in the argument string.
- #
- # 'Module'.constantize # => Module
- # 'Test::Unit'.constantize # => Test::Unit
- #
- # The name is assumed to be the one of a top-level constant, no matter
- # whether it starts with "::" or not. No lexical context is taken into
- # account:
- #
- # C = 'outside'
- # module M
- # C = 'inside'
- # C # => 'inside'
- # 'C'.constantize # => 'outside', same as ::C
- # end
- #
- # 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("::")
-
- # Trigger a built-in NameError exception including the ill-formed constant in the message.
- Object.const_get(camel_cased_word) if names.empty?
-
- # Remove the first blank element in case of '::ClassName' notation.
- names.shift if names.size > 1 && names.first.empty?
-
- names.inject(Object) do |constant, name|
- if constant == Object
- constant.const_get(name)
- else
- candidate = constant.const_get(name)
- next candidate if constant.const_defined?(name, false)
- next candidate unless Object.const_defined?(name)
-
- # Go down the ancestors to check if it is owned directly. The check
- # stops when we reach Object or the end of ancestors tree.
- constant = constant.ancestors.inject do |const, ancestor|
- break const if ancestor == Object
- break ancestor if ancestor.const_defined?(name, false)
- const
- end
-
- # owner is in Object, so raise
- constant.const_get(name, false)
- end
- end
- end
-
end
end
end
diff --git a/lib/chef/mixin/create_path.rb b/lib/chef/mixin/create_path.rb
index 233f7b9521..26988556aa 100644
--- a/lib/chef/mixin/create_path.rb
+++ b/lib/chef/mixin/create_path.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -29,11 +29,11 @@ class Chef
# === Returns
# The created file_path.
def create_path(file_path)
- unless file_path.kind_of?(String) || file_path.kind_of?(Array)
+ unless file_path.is_a?(String) || file_path.is_a?(Array)
raise ArgumentError, "file_path must be a string or an array!"
end
- if file_path.kind_of?(String)
+ if file_path.is_a?(String)
file_path = File.expand_path(file_path).split(File::SEPARATOR)
file_path.shift if file_path[0] == ""
# Check if path starts with a separator or drive letter (Windows)
@@ -53,19 +53,17 @@ class Chef
private
def create_dir(path)
- begin
- # When doing multithreaded downloads into the file cache, the following
- # interleaving raises an error here:
- #
- # thread1 thread2
- # File.directory?(create_path) <- false
- # File.directory?(create_path) <- false
- # Dir.mkdir(create_path)
- # Dir.mkdir(create_path) <- raises Errno::EEXIST
- Chef::Log.debug("Creating directory #{path}")
- Dir.mkdir(path)
- rescue Errno::EEXIST
- end
+ # When doing multithreaded downloads into the file cache, the following
+ # interleaving raises an error here:
+ #
+ # thread1 thread2
+ # File.directory?(create_path) <- false
+ # File.directory?(create_path) <- false
+ # Dir.mkdir(create_path)
+ # Dir.mkdir(create_path) <- raises Errno::EEXIST
+ Chef::Log.trace("Creating directory #{path}")
+ Dir.mkdir(path)
+ rescue Errno::EEXIST
end
end
diff --git a/lib/chef/mixin/deep_merge.rb b/lib/chef/mixin/deep_merge.rb
index c0b2d0d0c5..ad9c9e89dd 100644
--- a/lib/chef/mixin/deep_merge.rb
+++ b/lib/chef/mixin/deep_merge.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Steve Midgley (http://www.misuse.org/science)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# Copyright:: Copyright 2008-2016, Steve Midgley
# License:: Apache License, Version 2.0
#
@@ -19,52 +19,67 @@
class Chef
module Mixin
- # == Chef::Mixin::DeepMerge
# Implements a deep merging algorithm for nested data structures.
- # ==== Notice:
- # This code was originally imported from deep_merge by Steve Midgley.
- # deep_merge is available under the MIT license from
- # http://trac.misuse.org/science/wiki/DeepMerge
+ #
+ # This code was originally imported from deep_merge by Steve Midgley.
+ # deep_merge is available under the MIT license from
+ # http://trac.misuse.org/science/wiki/DeepMerge
+ #
+ # Note that this is not considered a public interface. It is technically
+ # public and has been used and we cannot break the API, but continued
+ # external use is discouraged. We are unlikely to change the shape of
+ # the API and break anyone, but this code does not serve the purposes of
+ # cookbook authors and customers. It is intended only for the purposes
+ # of the internal use in the chef-client codebase. We do not accept
+ # pull requests to extend the functionality of this algorithm. Users
+ # who find this does nearly what they want, should copy and paste the
+ # algorithm and tune to their needs. We will not maintain any additional
+ # use cases.
+ #
+ # "It is what it is, and if it isn't what you want, you need to build
+ # that yourself"
+ #
+ # @api private
+ #
module DeepMerge
extend self
+ # @api private
def merge(first, second)
- first = Mash.new(first) unless first.kind_of?(Mash)
- second = Mash.new(second) unless second.kind_of?(Mash)
+ first = Mash.new(first) unless first.is_a?(Mash)
+ second = Mash.new(second) unless second.is_a?(Mash)
DeepMerge.deep_merge(second, first)
end
class InvalidParameter < StandardError; end
- # Deep Merge core documentation.
# deep_merge! method permits merging of arbitrary child elements. The two top level
# elements must be hashes. These hashes can contain unlimited (to stack limit) levels
# of child elements. These child elements to not have to be of the same types.
# Where child elements are of the same type, deep_merge will attempt to merge them together.
# Where child elements are not of the same type, deep_merge will skip or optionally overwrite
# the destination element with the contents of the source element at that level.
+ #
# So if you have two hashes like this:
+ #
# source = {:x => [1,2,3], :y => 2}
# dest = {:x => [4,5,'6'], :y => [7,8,9]}
# dest.deep_merge!(source)
# Results: {:x => [1,2,3,4,5,'6'], :y => 2}
+ #
# By default, "deep_merge!" will overwrite any unmergeables and merge everything else.
# To avoid this, use "deep_merge" (no bang/exclamation mark)
+ #
+ # @api private
+ #
def deep_merge!(source, dest)
- # if dest doesn't exist, then simply copy source to it
- if dest.nil?
- dest = source; return dest
- end
-
case source
- when nil
- dest
when Hash
- if dest.kind_of?(Hash)
+ if dest.is_a?(Hash)
source.each do |src_key, src_value|
- if dest[src_key]
+ if dest.key?(src_key)
dest[src_key] = deep_merge!(src_value, dest[src_key])
else # dest[src_key] doesn't exist so we take whatever source has
dest[src_key] = src_value
@@ -74,8 +89,8 @@ class Chef
dest = source
end
when Array
- if dest.kind_of?(Array)
- dest = dest | source
+ if dest.is_a?(Array)
+ dest |= source
else
dest = source
end
@@ -87,10 +102,12 @@ class Chef
dest
end # deep_merge!
+ # @api private
def hash_only_merge(merge_onto, merge_with)
hash_only_merge!(safe_dup(merge_onto), safe_dup(merge_with))
end
+ # @api private
def safe_dup(thing)
thing.dup
rescue TypeError
@@ -101,12 +118,15 @@ class Chef
# `merge_onto` is the object that will "lose" in case of conflict.
# `merge_with` is the object whose values will replace `merge_onto`s
# values when there is a conflict.
+ #
+ # @api private
+ #
def hash_only_merge!(merge_onto, merge_with)
# If there are two Hashes, recursively merge.
- if merge_onto.kind_of?(Hash) && merge_with.kind_of?(Hash)
+ if merge_onto.is_a?(Hash) && merge_with.is_a?(Hash)
merge_with.each do |key, merge_with_value|
value =
- if merge_onto.has_key?(key)
+ if merge_onto.key?(key)
hash_only_merge(merge_onto[key], merge_with_value)
else
merge_with_value
@@ -120,17 +140,14 @@ class Chef
end
end
merge_onto
-
- # If merge_with is nil, don't replace merge_onto
- elsif merge_with.nil?
- merge_onto
-
# In all other cases, replace merge_onto with merge_with
else
merge_with
end
end
+ # @api private
+ #
def deep_merge(source, dest)
deep_merge!(safe_dup(source), safe_dup(dest))
end
diff --git a/lib/chef/provider/deploy/timestamped.rb b/lib/chef/mixin/default_paths.rb
index 5486b092d3..95f444c13f 100644
--- a/lib/chef/provider/deploy/timestamped.rb
+++ b/lib/chef/mixin/default_paths.rb
@@ -1,6 +1,5 @@
#
-# Author:: Daniel DeLeo (<dan@kallistec.com>)
-# Copyright:: Copyright 2009-2016, Daniel DeLeo
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,17 +15,16 @@
# limitations under the License.
#
-class Chef
- class Provider
- class Deploy
- class Timestamped < Chef::Provider::Deploy
- provides :timestamped_deploy
- provides :deploy
+require "chef-utils/dsl/default_paths" unless defined?(ChefUtils::DSL::DefaultPaths)
- protected
+class Chef
+ module Mixin
+ module DefaultPaths
+ include ChefUtils::DSL::DefaultPaths
- def release_slug
- Time.now.utc.strftime("%Y%m%d%H%M%S")
+ def enforce_default_paths(env = ENV)
+ if Chef::Config[:enforce_default_paths] || Chef::Config[:enforce_path_sanity]
+ env["PATH"] = default_paths(env)
end
end
end
diff --git a/lib/chef/mixin/deprecation.rb b/lib/chef/mixin/deprecation.rb
index 0f059a215f..638ddfd5ea 100644
--- a/lib/chef/mixin/deprecation.rb
+++ b/lib/chef/mixin/deprecation.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,24 +23,24 @@ class Chef
@deprecated_constants ||= {}
end
- # Add a deprecated constant to the Chef::Mixin namespace.
- # === Arguments
- # * name: the constant name, as a relative symbol.
- # * replacement: the constant to return instead.
- # * message: A message telling the user what to do instead.
- # === Example:
- # deprecate_constant(:RecipeDefinitionDSLCore, Chef::DSL::Recipe, <<-EOM)
- # Chef::Mixin::RecipeDefinitionDSLCore is deprecated, use Chef::DSL::Recipe instead.
- # EOM
+ # Add a deprecated constant to the Chef::Mixin namespace.
+ #
+ # @param name [Symbol] the constant name, as a relative symbol.
+ # @param replacement [Object] the constant to return instead.
+ # @param message [String] A message telling the user what to do instead.
+ # @example
+ # deprecate_constant(:RecipeDefinitionDSLCore, Chef::DSL::Recipe, <<-EOM)
+ # Chef::Mixin::RecipeDefinitionDSLCore is deprecated, use Chef::DSL::Recipe instead.
+ # EOM
def self.deprecate_constant(name, replacement, message)
- deprecated_constants[name] = { :replacement => replacement, :message => message }
+ deprecated_constants[name] = { replacement: replacement, message: message }
end
- # Const missing hook to look up deprecated constants defined with
- # deprecate_constant. Emits a warning to the logger and returns the
- # replacement constant. Will call super, most likely causing an exception
- # for the missing constant, if +name+ is not found in the
- # deprecated_constants collection.
+ # Const missing hook to look up deprecated constants defined with
+ # deprecate_constant. Emits a warning to the logger and returns the
+ # replacement constant. Will call super, most likely causing an exception
+ # for the missing constant, if +name+ is not found in the
+ # deprecated_constants collection.
def self.const_missing(name)
if new_const = deprecated_constants[name]
Chef::Log.warn(new_const[:message])
@@ -54,7 +54,7 @@ class Chef
module Deprecation
class DeprecatedObjectProxyBase
- KEEPERS = %w{__id__ __send__ instance_eval == equal? initialize object_id}
+ KEEPERS = %w{__id__ __send__ instance_eval == equal? initialize object_id}.freeze
instance_methods.each { |method_name| undef_method(method_name) unless KEEPERS.include?(method_name.to_s) }
end
@@ -65,7 +65,7 @@ class Chef
end
def method_missing(method_name, *args, &block)
- log_deprecation_msg(caller[0..3])
+ deprecated_msg(caller[0..3])
@target.send(method_name, *args, &block)
end
@@ -75,7 +75,7 @@ class Chef
private
- def log_deprecation_msg(*called_from)
+ def deprecated_msg(*called_from)
called_from = called_from.flatten
log("Accessing #{@ivar_name} by the variable @#{@ivar_name} is deprecated. Support will be removed in a future release.")
log("Please update your cookbooks to use #{@ivar_name} in place of @#{@ivar_name}. Accessed from:")
@@ -101,20 +101,14 @@ class Chef
def deprecated_attr_reader(name, alternative, level = :warn)
define_method(name) do
- Chef.log_deprecation("#{self.class}.#{name} is deprecated. Support will be removed in a future release.")
- Chef.log_deprecation(alternative)
- Chef.log_deprecation("Called from:")
- caller[0..3].each { |c| Chef.log_deprecation(c) }
+ Chef.deprecated(:internal_api, "#{self.class}.#{name} is deprecated. Support will be removed in a future release. #{alternative}")
instance_variable_get("@#{name}")
end
end
def deprecated_attr_writer(name, alternative, level = :warn)
define_method("#{name}=") do |value|
- Chef.log_deprecation("Writing to #{self.class}.#{name} with #{name}= is deprecated. Support will be removed in a future release.")
- Chef.log_deprecation(alternative)
- Chef.log_deprecation("Called from:")
- caller[0..3].each { |c| Chef.log_deprecation(c) }
+ Chef.deprecated(:internal_api, "Writing to #{self.class}.#{name} with #{name}= is deprecated. Support will be removed in a future release. #{alternative}")
instance_variable_set("@#{name}", value)
end
end
diff --git a/lib/chef/mixin/enforce_ownership_and_permissions.rb b/lib/chef/mixin/enforce_ownership_and_permissions.rb
index e02c34748f..20ecdbdcf7 100644
--- a/lib/chef/mixin/enforce_ownership_and_permissions.rb
+++ b/lib/chef/mixin/enforce_ownership_and_permissions.rb
@@ -1,6 +1,6 @@
#
# Author:: Seth Chisamore (<schisamo@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/file_access_control"
+require_relative "../file_access_control"
class Chef
module Mixin
diff --git a/lib/chef/mixin/file_class.rb b/lib/chef/mixin/file_class.rb
index 166dd5796a..6540427e43 100644
--- a/lib/chef/mixin/file_class.rb
+++ b/lib/chef/mixin/file_class.rb
@@ -2,7 +2,7 @@
# Author:: Mark Mzyk <mmzyk@chef.io>
# Author:: Seth Chisamore <schisamo@chef.io>
# Author:: Bryan McLellan <btm@chef.io>
-# Copyright:: Copyright 2011-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,8 +23,8 @@ class Chef
module FileClass
def file_class
- @host_os_file ||= if Chef::Platform.windows?
- require "chef/win32/file"
+ @host_os_file ||= if ChefUtils.windows?
+ require_relative "../win32/file"
Chef::ReservedNames::Win32::File
else
::File
diff --git a/lib/chef/mixin/from_file.rb b/lib/chef/mixin/from_file.rb
index a6692d798d..9ced026130 100644
--- a/lib/chef/mixin/from_file.rb
+++ b/lib/chef/mixin/from_file.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,13 +21,17 @@ class Chef
module Mixin
module FromFile
+ # Source path from which the object was loaded
+ attr_accessor :source_file
+
# Loads a given ruby file, and runs instance_eval against it in the context of the current
# object.
#
# Raises an IOError if the file cannot be found, or is not readable.
def from_file(filename)
- if File.exists?(filename) && File.readable?(filename)
- self.instance_eval(IO.read(filename), filename, 1)
+ self.source_file = filename
+ if File.file?(filename) && File.readable?(filename)
+ instance_eval(IO.read(filename), filename, 1)
else
raise IOError, "Cannot open or read #{filename}!"
end
@@ -38,8 +42,9 @@ class Chef
#
# Raises an IOError if the file cannot be found, or is not readable.
def class_from_file(filename)
- if File.exists?(filename) && File.readable?(filename)
- self.class_eval(IO.read(filename), filename, 1)
+ self.source_file = filename
+ if File.file?(filename) && File.readable?(filename)
+ class_eval(IO.read(filename), filename, 1)
else
raise IOError, "Cannot open or read #{filename}!"
end
diff --git a/lib/chef/mixin/get_source_from_package.rb b/lib/chef/mixin/get_source_from_package.rb
index 555dd634f8..88e09758cb 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@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,7 +18,7 @@
#
# mixin to make this syntax work without specifying a source:
#
-# gem_pacakge "/tmp/foo-x.y.z.gem"
+# gem_package "/tmp/foo-x.y.z.gem"
# rpm_package "/tmp/foo-x.y-z.rpm"
# dpkg_package "/tmp/foo-x.y.z.deb"
#
@@ -35,11 +35,12 @@ class Chef
def initialize(new_resource, run_context)
super
return if new_resource.package_name.is_a?(Array)
+
# if we're passed something that looks like a filesystem path, with no source, use it
# - require at least one '/' in the path to avoid gem_package "foo" breaking if a file named 'foo' exists in the cwd
- if new_resource.source.nil? && new_resource.package_name.match(/#{::File::SEPARATOR}/) && ::File.exists?(new_resource.package_name)
- Chef::Log.debug("No package source specified, but #{new_resource.package_name} exists on the filesystem, copying to package source")
- new_resource.source(@new_resource.package_name)
+ if new_resource.source.nil? && new_resource.package_name.include?(::File::SEPARATOR) && ::File.exist?(new_resource.package_name)
+ Chef::Log.trace("No package source specified, but #{new_resource.package_name} exists on the filesystem, copying to package source")
+ new_resource.source(new_resource.package_name)
end
end
end
diff --git a/lib/chef/mixin/homebrew_user.rb b/lib/chef/mixin/homebrew_user.rb
index 888c1bcbfd..36936f9578 100644
--- a/lib/chef/mixin/homebrew_user.rb
+++ b/lib/chef/mixin/homebrew_user.rb
@@ -2,8 +2,8 @@
# Author:: Joshua Timberman (<joshua@chef.io>)
# Author:: Graeme Mathieson (<mathie@woss.name>)
#
-# Copyright 2011-2016, Chef Software Inc.
-# Copyright 2014-2016, Chef Software, Inc <legal@chef.io>
+# Copyright:: Copyright (c) Chef Software Inc.
+# Copyright:: Copyright (c) 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.
@@ -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_relative "shell_out"
+require "etc" unless defined?(Etc)
class Chef
module Mixin
@@ -34,15 +34,27 @@ class Chef
# This tries to find the user to execute brew as. If a user is provided, that overrides the brew
# executable user. It is an error condition if the brew executable owner is root or we cannot find
# the brew executable.
+ # @param [String, Integer] provided_user
+ # @return [Integer] UID of the user
def find_homebrew_uid(provided_user = nil)
# They could provide us a user name or a UID
if provided_user
return provided_user if provided_user.is_a? Integer
+
return Etc.getpwnam(provided_user).uid
end
- @homebrew_owner ||= calculate_owner
- @homebrew_owner
+ @homebrew_owner_uid ||= calculate_owner
+ @homebrew_owner_uid
+ end
+
+ # Use find_homebrew_uid to return the UID and then lookup the
+ # name from that UID because sometimes you want the name not the UID
+ # @param [String, Integer] provided_user
+ # @return [String] username
+ def find_homebrew_username(provided_user = nil)
+ @homebrew_owner_username ||= Etc.getpwuid(find_homebrew_uid(provided_user)).name
+ @homebrew_owner_username
end
private
@@ -56,7 +68,7 @@ class Chef
owner = ::File.stat(brew_path).uid
else
raise Chef::Exceptions::CannotDetermineHomebrewOwner,
- 'Could not find the "brew" executable in /usr/local/bin or anywhere on the path.'
+ 'Could not find the "brew" executable in /usr/local/bin or anywhere on the path.'
end
Chef::Log.debug "Found Homebrew owner #{Etc.getpwuid(owner).name}; executing `brew` commands as them"
diff --git a/lib/chef/mixin/language.rb b/lib/chef/mixin/language.rb
deleted file mode 100644
index 3f53645a55..0000000000
--- a/lib/chef/mixin/language.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-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/dsl/platform_introspection"
-require "chef/dsl/data_query"
-require "chef/mixin/deprecation"
-
-class Chef
- module Mixin
-
- # == [DEPRECATED] Chef::Mixin::DeprecatedLanguageModule
- # This module is a temporary replacement for the previous
- # Chef::Mixin::Language. That module's functionality was split into two
- # modules, Chef::DSL::PlatformIntrospection, and Chef::DSL::DataQuery.
- #
- # This module includes both PlatformIntrospection and DataQuery to provide
- # the same interfaces and behavior as the prior Mixin::Language.
- #
- # This module is loaded via const_missing hook when Chef::Mixin::Language
- # is accessed. See chef/mixin/deprecation for details.
- module DeprecatedLanguageModule
-
- include Chef::DSL::PlatformIntrospection
- include Chef::DSL::DataQuery
-
- end
-
- deprecate_constant(:Language, DeprecatedLanguageModule, <<-EOM)
-Chef::Mixin::Language is deprecated. Use either (or both)
-Chef::DSL::PlatformIntrospection or Chef::DSL::DataQuery instead.
-EOM
- end
-end
diff --git a/lib/chef/mixin/language_include_attribute.rb b/lib/chef/mixin/language_include_attribute.rb
deleted file mode 100644
index 7cb66dc272..0000000000
--- a/lib/chef/mixin/language_include_attribute.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-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/dsl/include_attribute"
-require "chef/mixin/deprecation"
-
-class Chef
- module Mixin
-
- # DEPRECATED: This is just here for compatibility, use
- # Chef::DSL::IncludeAttribute instead.
-
- deprecate_constant(:LanguageIncludeAttribute, Chef::DSL::IncludeAttribute, <<-EOM)
-Chef::Mixin::LanguageIncludeAttribute is deprecated. Use
-Chef::DSL::IncludeAttribute instead.
-EOM
-
- end
-end
diff --git a/lib/chef/mixin/lazy_module_include.rb b/lib/chef/mixin/lazy_module_include.rb
index 34e1fce4f1..f7ca9cb14b 100644
--- a/lib/chef/mixin/lazy_module_include.rb
+++ b/lib/chef/mixin/lazy_module_include.rb
@@ -1,5 +1,5 @@
#
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/lib/chef/mixin/notifying_block.rb b/lib/chef/mixin/notifying_block.rb
index 2d6a82f493..341282d563 100644
--- a/lib/chef/mixin/notifying_block.rb
+++ b/lib/chef/mixin/notifying_block.rb
@@ -1,6 +1,6 @@
#--
# Author:: Lamont Granquist <lamont@chef.io>
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,15 +20,13 @@ class Chef
module NotifyingBlock
def notifying_block(&block)
- begin
- subcontext = subcontext_block(&block)
- Chef::Runner.new(subcontext).converge
- ensure
- # recipes don't have a new_resource
- if respond_to?(:new_resource)
- if subcontext && subcontext.resource_collection.any?(&:updated?)
- new_resource.updated_by_last_action(true)
- end
+ subcontext = subcontext_block(&block)
+ Chef::Runner.new(subcontext).converge
+ ensure
+ # recipes don't have a new_resource
+ if respond_to?(:new_resource)
+ if subcontext && subcontext.resource_collection.any?(&:updated?)
+ new_resource.updated_by_last_action(true)
end
end
end
diff --git a/lib/chef/mixin/openssl_helper.rb b/lib/chef/mixin/openssl_helper.rb
new file mode 100644
index 0000000000..69583bea98
--- /dev/null
+++ b/lib/chef/mixin/openssl_helper.rb
@@ -0,0 +1,448 @@
+#
+# Copyright:: Copyright (c) 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.
+#
+autoload :OpenSSL, "openssl"
+
+class Chef
+ module Mixin
+ # various helpers for use with openssl. Currently used by the openssl_* resources
+ module OpenSSLHelper
+ # determine the key filename from the cert filename
+ # @param [String] cert_filename the path to the certfile
+ # @return [String] the path to the keyfile
+ def get_key_filename(cert_filename)
+ cert_file_path, cert_filename = ::File.split(cert_filename)
+ cert_filename = ::File.basename(cert_filename, ::File.extname(cert_filename))
+ cert_file_path + ::File::SEPARATOR + cert_filename + ".key"
+ end
+
+ # is the key length a valid key length
+ # @param [Integer] number
+ # @return [Boolean] is length valid
+ def key_length_valid?(number)
+ number >= 1024 && ( number & (number - 1) == 0 )
+ end
+
+ # validate a dhparam file from path
+ # @param [String] dhparam_pem_path the path to the pem file
+ # @return [Boolean] is the key valid
+ def dhparam_pem_valid?(dhparam_pem_path)
+ # Check if the dhparam.pem file exists
+ # Verify the dhparam.pem file contains a key
+ return false unless ::File.exist?(dhparam_pem_path)
+
+ dhparam = ::OpenSSL::PKey::DH.new File.read(dhparam_pem_path)
+ dhparam.params_ok?
+ end
+
+ # given either a key file path or key file content see if it's actually
+ # a private key
+ # @param [String] key_file the path to the keyfile or the key contents
+ # @param [String] key_password optional password to the keyfile
+ # @return [Boolean] is the key valid?
+ def priv_key_file_valid?(key_file, key_password = nil)
+ # if the file exists try to read the content
+ # if not assume we were passed the key and set the string to the content
+ key_content = ::File.exist?(key_file) ? File.read(key_file) : key_file
+
+ begin
+ key = ::OpenSSL::PKey.read key_content, key_password
+ rescue ::OpenSSL::PKey::PKeyError, ArgumentError
+ return false
+ end
+
+ if key.is_a?(::OpenSSL::PKey::EC)
+ key.private_key?
+ else
+ key.private?
+ end
+ end
+
+ # given a crl file path see if it's actually a crl
+ # @param [String] crl_file the path to the crlfile
+ # @return [Boolean] is the key valid?
+ def crl_file_valid?(crl_file)
+ begin
+ ::OpenSSL::X509::CRL.new ::File.read(crl_file)
+ rescue ::OpenSSL::X509::CRLError, Errno::ENOENT
+ return false
+ end
+ true
+ end
+
+ # check is a serial given is revoked in a crl given
+ # @param [OpenSSL::X509::CRL] crl X509 CRL to check
+ # @param [String, Integer] serial X509 Certificate Serial Number
+ # @return [true, false]
+ def serial_revoked?(crl, serial)
+ raise TypeError, "crl must be a Ruby OpenSSL::X509::CRL object" unless crl.is_a?(::OpenSSL::X509::CRL)
+ raise TypeError, "serial must be a Ruby String or Integer object" unless serial.is_a?(String) || serial.is_a?(Integer)
+
+ serial_to_verify = if serial.is_a?(String)
+ serial.to_i(16)
+ else
+ serial
+ end
+ status = false
+ crl.revoked.each do |revoked|
+ status = true if revoked.serial == serial_to_verify
+ end
+ status
+ end
+
+ # generate a dhparam file
+ # @param [String] key_length the length of the key
+ # @param [Integer] generator the dhparam generator to use
+ # @return [OpenSSL::PKey::DH]
+ def gen_dhparam(key_length, generator)
+ raise ArgumentError, "Key length must be a power of 2 greater than or equal to 1024" unless key_length_valid?(key_length)
+ raise TypeError, "Generator must be an integer" unless generator.is_a?(Integer)
+
+ ::OpenSSL::PKey::DH.new(key_length, generator)
+ end
+
+ # generate an RSA private key given key length
+ # @param [Integer] key_length the key length of the private key
+ # @return [OpenSSL::PKey::DH]
+ def gen_rsa_priv_key(key_length)
+ raise ArgumentError, "Key length must be a power of 2 greater than or equal to 1024" unless key_length_valid?(key_length)
+
+ ::OpenSSL::PKey::RSA.new(key_length)
+ end
+
+ # generate pem format of the public key given a private key
+ # @param [String] priv_key either the contents of the private key or the path to the file
+ # @param [String] priv_key_password optional password for the private key
+ # @return [String] pem format of the public key
+ def gen_rsa_pub_key(priv_key, priv_key_password = nil)
+ # if the file exists try to read the content
+ # if not assume we were passed the key and set the string to the content
+ key_content = ::File.exist?(priv_key) ? File.read(priv_key) : priv_key
+ key = ::OpenSSL::PKey::RSA.new key_content, priv_key_password
+ key.public_key.to_pem
+ end
+
+ # generate a pem file given a cipher, key, an optional key_password
+ # @param [OpenSSL::PKey::RSA] rsa_key the private key object
+ # @param [String] key_password the password for the private key
+ # @param [String] key_cipher the cipher to use
+ # @return [String] pem contents
+ def encrypt_rsa_key(rsa_key, key_password, key_cipher)
+ raise TypeError, "rsa_key must be a Ruby OpenSSL::PKey::RSA object" unless rsa_key.is_a?(::OpenSSL::PKey::RSA)
+ raise TypeError, "key_password must be a string" unless key_password.is_a?(String)
+ raise TypeError, "key_cipher must be a string" unless key_cipher.is_a?(String)
+ raise ArgumentError, "Specified key_cipher is not available on this system" unless ::OpenSSL::Cipher.ciphers.include?(key_cipher)
+
+ cipher = ::OpenSSL::Cipher.new(key_cipher)
+ rsa_key.to_pem(cipher, key_password)
+ end
+
+ # generate an ec private key given curve type
+ # @param [String] curve the kind of curve to use
+ # @return [OpenSSL::PKey::DH]
+ def gen_ec_priv_key(curve)
+ raise TypeError, "curve must be a string" unless curve.is_a?(String)
+ raise ArgumentError, "Specified curve is not available on this system" unless %w{prime256v1 secp384r1 secp521r1}.include?(curve)
+
+ ::OpenSSL::PKey::EC.new(curve).generate_key
+ end
+
+ # generate pem format of the public key given a private key
+ # @param [String] priv_key either the contents of the private key or the path to the file
+ # @param [String] priv_key_password optional password for the private key
+ # @return [String] pem format of the public key
+ def gen_ec_pub_key(priv_key, priv_key_password = nil)
+ # if the file exists try to read the content
+ # if not assume we were passed the key and set the string to the content
+ key_content = ::File.exist?(priv_key) ? File.read(priv_key) : priv_key
+ key = ::OpenSSL::PKey::EC.new key_content, priv_key_password
+
+ # Get curve type (prime256v1...)
+ group = ::OpenSSL::PKey::EC::Group.new(key.group.curve_name)
+ # Get Generator point & public point (priv * generator)
+ generator = group.generator
+ pub_point = generator.mul(key.private_key)
+ key.public_key = pub_point
+
+ # Public Key in pem
+ public_key = ::OpenSSL::PKey::EC.new
+ public_key.group = group
+ public_key.public_key = pub_point
+ public_key.to_pem
+ end
+
+ # generate a pem file given a cipher, key, an optional key_password
+ # @param [OpenSSL::PKey::EC] ec_key the private key object
+ # @param [String] key_password the password for the private key
+ # @param [String] key_cipher the cipher to use
+ # @return [String] pem contents
+ def encrypt_ec_key(ec_key, key_password, key_cipher)
+ raise TypeError, "ec_key must be a Ruby OpenSSL::PKey::EC object" unless ec_key.is_a?(::OpenSSL::PKey::EC)
+ raise TypeError, "key_password must be a string" unless key_password.is_a?(String)
+ raise TypeError, "key_cipher must be a string" unless key_cipher.is_a?(String)
+ raise ArgumentError, "Specified key_cipher is not available on this system" unless ::OpenSSL::Cipher.ciphers.include?(key_cipher)
+
+ cipher = ::OpenSSL::Cipher.new(key_cipher)
+ ec_key.to_pem(cipher, key_password)
+ end
+
+ # generate a csr pem file given a subject and a private key
+ # @param [OpenSSL::X509::Name] subject the x509 subject object
+ # @param [OpenSSL::PKey::EC, OpenSSL::PKey::RSA] key the private key object
+ # @return [OpenSSL::X509::Request]
+ def gen_x509_request(subject, key)
+ raise TypeError, "subject must be a Ruby OpenSSL::X509::Name object" unless subject.is_a?(::OpenSSL::X509::Name)
+ raise TypeError, "key must be a Ruby OpenSSL::PKey::EC or a Ruby OpenSSL::PKey::RSA object" unless key.is_a?(::OpenSSL::PKey::EC) || key.is_a?(::OpenSSL::PKey::RSA)
+
+ request = ::OpenSSL::X509::Request.new
+ request.version = 0
+ request.subject = subject
+ request.public_key = key
+
+ # Chef 12 backward compatibility
+ ::OpenSSL::PKey::EC.send(:alias_method, :private?, :private_key?)
+
+ request.sign(key, ::OpenSSL::Digest.new("SHA256"))
+ request
+ end
+
+ # generate an array of X509 Extensions given a hash of extensions
+ # @param [Hash] extensions hash of extensions
+ # @return [Array]
+ def gen_x509_extensions(extensions)
+ raise TypeError, "extensions must be a Ruby Hash object" unless extensions.is_a?(Hash)
+
+ exts = []
+ extensions.each do |ext_name, ext_prop|
+ raise TypeError, "#{ext_name} must contain a Ruby Hash" unless ext_prop.is_a?(Hash)
+ raise ArgumentError, "keys in #{ext_name} must be 'values' and 'critical'" unless ext_prop.key?("values") && ext_prop.key?("critical")
+ raise TypeError, "the key 'values' must contain a Ruby Arrays" unless ext_prop["values"].is_a?(Array)
+ raise TypeError, "the key 'critical' must be a Ruby Boolean true/false" unless ext_prop["critical"].is_a?(TrueClass) || ext_prop["critical"].is_a?(FalseClass)
+
+ exts << ::OpenSSL::X509::ExtensionFactory.new.create_extension(ext_name, ext_prop["values"].join(","), ext_prop["critical"])
+ end
+ exts
+ end
+
+ # generate a random Serial
+ # @return [Integer]
+ def gen_serial
+ ::OpenSSL::BN.generate_prime(160)
+ end
+
+ # generate a Certificate given a X509 request
+ # @param [OpenSSL::X509::Request] request X509 Certificate Request
+ # @param [Array] extension Array of X509 Certificate Extension
+ # @param [Hash] info issuer & validity
+ # @param [OpenSSL::PKey::EC, OpenSSL::PKey::RSA] key private key to sign with
+ # @return [OpenSSL::X509::Certificate]
+ def gen_x509_cert(request, extension, info, key)
+ raise TypeError, "request must be a Ruby OpenSSL::X509::Request" unless request.is_a?(::OpenSSL::X509::Request)
+ raise TypeError, "extension must be a Ruby Array" unless extension.is_a?(Array)
+ raise TypeError, "info must be a Ruby Hash" unless info.is_a?(Hash)
+ raise TypeError, "key must be a Ruby OpenSSL::PKey::EC object or a Ruby OpenSSL::PKey::RSA object" unless key.is_a?(::OpenSSL::PKey::EC) || key.is_a?(::OpenSSL::PKey::RSA)
+
+ raise ArgumentError, "info must contain a validity" unless info.key?("validity")
+ raise TypeError, "info['validity'] must be a Ruby Integer object" unless info["validity"].is_a?(Integer)
+
+ cert = ::OpenSSL::X509::Certificate.new
+ ef = ::OpenSSL::X509::ExtensionFactory.new
+
+ cert.serial = gen_serial
+ cert.version = 2
+ cert.subject = request.subject
+ cert.public_key = request.public_key
+ cert.not_before = Time.now
+ cert.not_after = cert.not_before + info["validity"] * 24 * 60 * 60
+
+ if info["issuer"].nil?
+ cert.issuer = request.subject
+ ef.issuer_certificate = cert
+ extension << ef.create_extension("basicConstraints", "CA:TRUE", true)
+ else
+ raise TypeError, "info['issuer'] must be a Ruby OpenSSL::X509::Certificate object" unless info["issuer"].is_a?(::OpenSSL::X509::Certificate)
+
+ cert.issuer = info["issuer"].subject
+ ef.issuer_certificate = info["issuer"]
+ end
+ ef.subject_certificate = cert
+ if openssl_config = __openssl_config
+ ef.config = openssl_config
+ end
+
+ cert.extensions = extension
+ cert.add_extension ef.create_extension("subjectKeyIdentifier", "hash")
+ cert.add_extension ef.create_extension("authorityKeyIdentifier",
+ "keyid:always,issuer:always")
+
+ cert.sign(key, ::OpenSSL::Digest.new("SHA256"))
+ cert
+ end
+
+ # generate a X509 CRL given a CA
+ # @param [OpenSSL::PKey::EC, OpenSSL::PKey::RSA] ca_private_key private key from the CA
+ # @param [Hash] info issuer & validity
+ # @return [OpenSSL::X509::CRL]
+ def gen_x509_crl(ca_private_key, info)
+ raise TypeError, "ca_private_key must be a Ruby OpenSSL::PKey::EC object or a Ruby OpenSSL::PKey::RSA object" unless ca_private_key.is_a?(::OpenSSL::PKey::EC) || ca_private_key.is_a?(::OpenSSL::PKey::RSA)
+ raise TypeError, "info must be a Ruby Hash" unless info.is_a?(Hash)
+
+ raise ArgumentError, "info must contain a issuer and a validity" unless info.key?("issuer") && info.key?("validity")
+ raise TypeError, "info['issuer'] must be a Ruby OpenSSL::X509::Certificate object" unless info["issuer"].is_a?(::OpenSSL::X509::Certificate)
+ raise TypeError, "info['validity'] must be a Ruby Integer object" unless info["validity"].is_a?(Integer)
+
+ crl = ::OpenSSL::X509::CRL.new
+ ef = ::OpenSSL::X509::ExtensionFactory.new
+
+ crl.version = 1
+ crl.issuer = info["issuer"].subject
+ crl.last_update = Time.now
+ crl.next_update = Time.now + 3600 * 24 * info["validity"]
+
+ if openssl_config = __openssl_config
+ ef.config = openssl_config
+ end
+ ef.issuer_certificate = info["issuer"]
+
+ crl.add_extension ::OpenSSL::X509::Extension.new("crlNumber", ::OpenSSL::ASN1::Integer(1))
+ crl.add_extension ef.create_extension("authorityKeyIdentifier",
+ "keyid:always,issuer:always")
+ crl.sign(ca_private_key, ::OpenSSL::Digest.new("SHA256"))
+ crl
+ end
+
+ # generate the next CRL number available for a X509 CRL given
+ # @param [OpenSSL::X509::CRL] crl x509 CRL
+ # @return [Integer]
+ def get_next_crl_number(crl)
+ raise TypeError, "crl must be a Ruby OpenSSL::X509::CRL object" unless crl.is_a?(::OpenSSL::X509::CRL)
+
+ crlnum = 1
+ crl.extensions.each do |e|
+ crlnum = e.value if e.oid == "crlNumber"
+ end
+ crlnum.to_i + 1
+ end
+
+ # add a serial given in the crl given
+ # @param [Hash] revoke_info serial to revoke & revocation reason
+ # @param [OpenSSL::X509::CRL] crl X509 CRL
+ # @param [OpenSSL::PKey::EC, OpenSSL::PKey::RSA] ca_private_key private key from the CA
+ # @param [Hash] info issuer & validity
+ # @return [OpenSSL::X509::CRL]
+ def revoke_x509_crl(revoke_info, crl, ca_private_key, info)
+ raise TypeError, "revoke_info must be a Ruby Hash object" unless revoke_info.is_a?(Hash)
+ raise TypeError, "crl must be a Ruby OpenSSL::X509::CRL object" unless crl.is_a?(::OpenSSL::X509::CRL)
+ raise TypeError, "ca_private_key must be a Ruby OpenSSL::PKey::EC object or a Ruby OpenSSL::PKey::RSA object" unless ca_private_key.is_a?(::OpenSSL::PKey::EC) || ca_private_key.is_a?(::OpenSSL::PKey::RSA)
+ raise TypeError, "info must be a Ruby Hash" unless info.is_a?(Hash)
+
+ raise ArgumentError, "revoke_info must contain a serial and a reason" unless revoke_info.key?("serial") && revoke_info.key?("reason")
+ raise TypeError, "revoke_info['serial'] must be a Ruby String or Integer object" unless revoke_info["serial"].is_a?(String) || revoke_info["serial"].is_a?(Integer)
+ raise TypeError, "revoke_info['reason'] must be a Ruby Integer object" unless revoke_info["reason"].is_a?(Integer)
+
+ raise ArgumentError, "info must contain a issuer and a validity" unless info.key?("issuer") && info.key?("validity")
+ raise TypeError, "info['issuer'] must be a Ruby OpenSSL::X509::Certificate object" unless info["issuer"].is_a?(::OpenSSL::X509::Certificate)
+ raise TypeError, "info['validity'] must be a Ruby Integer object" unless info["validity"].is_a?(Integer)
+
+ revoked = ::OpenSSL::X509::Revoked.new
+ revoked.serial = if revoke_info["serial"].is_a?(String)
+ revoke_info["serial"].to_i(16)
+ else
+ revoke_info["serial"]
+ end
+ revoked.time = Time.now
+
+ ext = ::OpenSSL::X509::Extension.new("CRLReason",
+ ::OpenSSL::ASN1::Enumerated(revoke_info["reason"]))
+ revoked.add_extension(ext)
+ crl.add_revoked(revoked)
+
+ renew_x509_crl(crl, ca_private_key, info)
+ end
+
+ # renew a X509 crl given
+ # @param [OpenSSL::X509::CRL] crl CRL to renew
+ # @param [OpenSSL::PKey::EC, OpenSSL::PKey::RSA] ca_private_key private key from the CA
+ # @param [Hash] info issuer & validity
+ # @return [OpenSSL::X509::CRL]
+ def renew_x509_crl(crl, ca_private_key, info)
+ raise TypeError, "crl must be a Ruby OpenSSL::X509::CRL object" unless crl.is_a?(::OpenSSL::X509::CRL)
+ raise TypeError, "ca_private_key must be a Ruby OpenSSL::PKey::EC object or a Ruby OpenSSL::PKey::RSA object" unless ca_private_key.is_a?(::OpenSSL::PKey::EC) || ca_private_key.is_a?(::OpenSSL::PKey::RSA)
+ raise TypeError, "info must be a Ruby Hash" unless info.is_a?(Hash)
+
+ raise ArgumentError, "info must contain a issuer and a validity" unless info.key?("issuer") && info.key?("validity")
+ raise TypeError, "info['issuer'] must be a Ruby OpenSSL::X509::Certificate object" unless info["issuer"].is_a?(::OpenSSL::X509::Certificate)
+ raise TypeError, "info['validity'] must be a Ruby Integer object" unless info["validity"].is_a?(Integer)
+
+ crl.last_update = Time.now
+ crl.next_update = crl.last_update + 3600 * 24 * info["validity"]
+
+ ef = ::OpenSSL::X509::ExtensionFactory.new
+ if openssl_config = __openssl_config
+ ef.config = openssl_config
+ end
+ ef.issuer_certificate = info["issuer"]
+
+ crl.extensions = [ ::OpenSSL::X509::Extension.new("crlNumber",
+ ::OpenSSL::ASN1::Integer(get_next_crl_number(crl)))]
+ crl.add_extension ef.create_extension("authorityKeyIdentifier",
+ "keyid:always,issuer:always")
+ crl.sign(ca_private_key, ::OpenSSL::Digest.new("SHA256"))
+ crl
+ end
+
+ # Return true if a certificate need to be renewed (or doesn't exist) according to the number
+ # of days before expiration given
+ # @param [string] cert_file path of the cert file or cert content
+ # @param [integer] renew_before_expiry number of days before expiration
+ # @return [true, false]
+ def cert_need_renewal?(cert_file, renew_before_expiry)
+ resp = true
+ cert_content = ::File.exist?(cert_file) ? File.read(cert_file) : cert_file
+ begin
+ cert = OpenSSL::X509::Certificate.new cert_content
+ rescue ::OpenSSL::X509::CertificateError
+ return resp
+ end
+
+ unless cert.not_after <= Time.now + 3600 * 24 * renew_before_expiry
+ resp = false
+ end
+
+ resp
+ end
+
+ alias_method :cert_need_renewall?, :cert_need_renewal?
+
+ private
+
+ def __openssl_config
+ path = if File.exist?(::OpenSSL::Config::DEFAULT_CONFIG_FILE)
+ OpenSSL::Config::DEFAULT_CONFIG_FILE
+ else
+ Dir[File.join(RbConfig::CONFIG["prefix"], "**", "openssl.cnf")].first
+ end
+
+ if File.exist?(path)
+ ::OpenSSL::Config.load(path)
+ else
+ Chef::Log.warn("Couldn't find OpenSSL config file")
+ nil
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/mixin/params_validate.rb b/lib/chef/mixin/params_validate.rb
index b16df41c8e..5259bf4449 100644
--- a/lib/chef/mixin/params_validate.rb
+++ b/lib/chef/mixin/params_validate.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,9 +15,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require "chef/constants"
-require "chef/property"
-require "chef/delayed_evaluator"
+require_relative "../constants"
+require_relative "../property"
+require_relative "../delayed_evaluator"
+require_relative "../exceptions"
class Chef
module Mixin
@@ -34,12 +35,14 @@ class Chef
# map options are:
#
# @param opts [Hash<Symbol,Object>] Validation opts.
+ # @option opts [String] :validation_message A custom message to return
+ # should validation fail.
# @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.)
+ # operator (`opts[:equal_to].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)
@@ -87,23 +90,27 @@ class Chef
# looking for _pv_:symbol as methods. Assuming it find them, it calls the right
# one.
#++
- raise ArgumentError, "Options must be a hash" unless opts.kind_of?(Hash)
- raise ArgumentError, "Validation Map must be a hash" unless map.kind_of?(Hash)
+ raise ArgumentError, "Options must be a hash" unless opts.is_a?(Hash)
+ raise ArgumentError, "Validation Map must be a hash" unless map.is_a?(Hash)
+
+ @validation_message ||= {}
map.each do |key, validation|
- unless key.kind_of?(Symbol) || key.kind_of?(String)
+ unless key.is_a?(Symbol) || key.is_a?(String)
raise ArgumentError, "Validation map keys must be symbols or strings!"
end
+
case validation
when true
_pv_required(opts, key)
when false
true
when Hash
+ @validation_message[key] = validation.delete(:validation_message) if validation.key?(:validation_message)
validation.each do |check, carg|
check_method = "_pv_#{check}"
- if self.respond_to?(check_method, true)
- self.send(check_method, opts, key, carg)
+ if respond_to?(check_method, true)
+ send(check_method, opts, key, carg)
else
raise ArgumentError, "Validation map has unknown check: #{check}"
end
@@ -124,15 +131,15 @@ class Chef
private
- def explicitly_allows_nil?(key, validation)
- validation.has_key?(:is) && _pv_is({ key => nil }, key, validation[:is], raise_error: false)
+ def _validation_message(key, default)
+ @validation_message.key?(key) ? @validation_message[key] : default
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)
+ if opts.key?(key.to_s)
opts[key.to_s]
- elsif opts.has_key?(key.to_sym)
+ elsif opts.key?(key.to_sym)
opts[key.to_sym]
else
nil
@@ -142,9 +149,10 @@ class Chef
# 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!"
+ return true if opts.key?(key.to_s) && (explicitly_allows_nil || !opts[key.to_s].nil?)
+ return true if opts.key?(key.to_sym) && (explicitly_allows_nil || !opts[key.to_sym].nil?)
+
+ raise Exceptions::ValidationFailed, _validation_message(key, "Required argument #{key.inspect} is missing!")
end
true
end
@@ -167,7 +175,9 @@ class Chef
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}."
+ # Ruby will print :something as something, which confuses users so make sure to print them as symbols
+ # by inspecting the value instead of just printing it
+ raise Exceptions::ValidationFailed, _validation_message(key, "Option #{key} must be equal to one of: #{to_be.map(&:inspect).join(", ")}! You passed #{value.inspect}.")
end
end
@@ -184,9 +194,9 @@ class Chef
unless value.nil?
to_be = Array(to_be)
to_be.each do |tb|
- return true if value.kind_of?(tb)
+ return true if value.is_a?(tb)
end
- raise Exceptions::ValidationFailed, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}."
+ raise Exceptions::ValidationFailed, _validation_message(key, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}.")
end
end
@@ -201,7 +211,7 @@ class Chef
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!"
+ raise Exceptions::ValidationFailed, _validation_message(key, "Option #{key} must have a #{method_name} method!")
end
end
end
@@ -227,13 +237,13 @@ class Chef
#
def _pv_cannot_be(opts, key, predicate_method_base_name)
value = _pv_opts_lookup(opts, key)
- if !value.nil?
+ unless 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}"
+ raise Exceptions::ValidationFailed, _validation_message(key, "Option #{key} cannot be #{predicate_method_base_name}")
end
end
end
@@ -269,7 +279,7 @@ class Chef
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)
+ default_value = default_value.freeze unless default_value.is_a?(DelayedEvaluator)
opts[key] = default_value
end
end
@@ -289,11 +299,11 @@ class Chef
#
def _pv_regex(opts, key, regex)
value = _pv_opts_lookup(opts, key)
- if !value.nil?
+ unless 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}"
+ raise Exceptions::ValidationFailed, _validation_message(key, "Property #{key}'s value #{value} does not match regular expression #{regex.inspect}")
end
end
@@ -310,12 +320,13 @@ class Chef
# ```
#
def _pv_callbacks(opts, key, callbacks)
- raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash)
+ raise ArgumentError, "Callback list must be a hash!" unless callbacks.is_a?(Hash)
+
value = _pv_opts_lookup(opts, key)
- if !value.nil?
+ unless value.nil?
callbacks.each do |message, zeproc|
unless zeproc.call(value)
- raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} #{message}!"
+ raise Exceptions::ValidationFailed, _validation_message(key, "Option #{key}'s value #{value} #{message}!")
end
end
end
@@ -332,8 +343,9 @@ class Chef
def _pv_name_property(opts, key, is_name_property = true)
if is_name_property
if opts[key].nil?
- raise CannotValidateStaticallyError, "name_property cannot be evaluated without a resource." if self == Chef::Mixin::ParamsValidate
- opts[key] = self.instance_variable_get(:"@name")
+ raise Exceptions::CannotValidateStaticallyError, "name_property cannot be evaluated without a resource." if self == Chef::Mixin::ParamsValidate
+
+ opts[key] = instance_variable_get(:"@name")
end
end
end
@@ -396,15 +408,17 @@ class Chef
# 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)
+ def _pv_is(opts, key, to_be)
+ return true if !opts.key?(key.to_s) && !opts.key?(key.to_sym)
+
value = _pv_opts_lookup(opts, key)
to_be = [ to_be ].flatten(1)
errors = []
passed = to_be.any? do |tb|
case tb
when Proc
- raise CannotValidateStaticallyError, "is: proc { } must be evaluated once for each resource" if self == Chef::Mixin::ParamsValidate
+ raise Exceptions::CannotValidateStaticallyError, "is: proc { } must be evaluated once for each resource" if self == Chef::Mixin::ParamsValidate
+
instance_exec(value, &tb)
when Property
begin
@@ -413,6 +427,7 @@ class Chef
rescue Exceptions::ValidationFailed
# re-raise immediately if there is only one "is" so we get a better stack
raise if to_be.size == 1
+
errors << $!
false
end
@@ -423,11 +438,11 @@ class Chef
if passed
true
else
- message = "Property #{key} must be one of: #{to_be.map { |v| v.inspect }.join(", ")}! You passed #{value.inspect}."
+ message = "Property #{key} must be one of: #{to_be.map(&:inspect).join(", ")}! You passed #{value.inspect}."
unless errors.empty?
message << " Errors:\n#{errors.map { |m| "- #{m}" }.join("\n")}"
end
- raise Exceptions::ValidationFailed, message
+ raise Exceptions::ValidationFailed, _validation_message(key, message)
end
end
@@ -447,11 +462,13 @@ class Chef
# ```
#
def _pv_coerce(opts, key, coercer)
- if opts.has_key?(key.to_s)
- raise CannotValidateStaticallyError, "coerce must be evaluated for each resource." if self == Chef::Mixin::ParamsValidate
+ if opts.key?(key.to_s)
+ raise Exceptions::CannotValidateStaticallyError, "coerce must be evaluated for each resource." if self == Chef::Mixin::ParamsValidate
+
opts[key.to_s] = instance_exec(opts[key], &coercer)
- elsif opts.has_key?(key.to_sym)
- raise CannotValidateStaticallyError, "coerce must be evaluated for each resource." if self == Chef::Mixin::ParamsValidate
+ elsif opts.key?(key.to_sym)
+ raise Exceptions::CannotValidateStaticallyError, "coerce must be evaluated for each resource." if self == Chef::Mixin::ParamsValidate
+
opts[key.to_sym] = instance_exec(opts[key], &coercer)
end
end
@@ -469,7 +486,7 @@ class Chef
def get(resource, nil_set: false)
value = super
# All values are sticky, frozen or not
- if !is_set?(resource)
+ unless is_set?(resource)
set_value(resource, value)
end
value
diff --git a/lib/chef/mixin/path_sanity.rb b/lib/chef/mixin/path_sanity.rb
index 6a8e017bcd..d9ca74bebf 100644
--- a/lib/chef/mixin/path_sanity.rb
+++ b/lib/chef/mixin/path_sanity.rb
@@ -1,6 +1,6 @@
#
# Author:: Seth Chisamore (<schisamo@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,53 +16,16 @@
# limitations under the License.
#
+require_relative "default_paths"
+
class Chef
module Mixin
module PathSanity
+ include Chef::Mixin::DefaultPaths
def enforce_path_sanity(env = ENV)
- if Chef::Config[:enforce_path_sanity]
- env["PATH"] = "" if env["PATH"].nil?
- 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
- paths_to_add = []
- paths_to_add << ruby_bindir unless sane_paths.include?(ruby_bindir)
- paths_to_add << gem_bindir unless sane_paths.include?(gem_bindir)
- paths_to_add << sane_paths if sane_paths
- paths_to_add.flatten!.compact!
- paths_to_add.each do |sane_path|
- unless existing_paths.include?(sane_path)
- env_path = env["PATH"].dup
- env_path << path_separator unless env["PATH"].empty?
- env_path << sane_path
- env["PATH"] = env_path.encode("utf-8", invalid: :replace, undef: :replace)
- end
- end
- end
+ enforce_default_paths(env)
end
-
- private
-
- def sane_paths
- @sane_paths ||= begin
- if Chef::Platform.windows?
- %w{}
- else
- %w{/usr/local/sbin /usr/local/bin /usr/sbin /usr/bin /sbin /bin}
- end
- end
- end
-
- def ruby_bindir
- RbConfig::CONFIG["bindir"]
- end
-
- def gem_bindir
- Gem.bindir
- end
-
end
end
end
diff --git a/lib/chef/mixin/powershell_exec.rb b/lib/chef/mixin/powershell_exec.rb
new file mode 100644
index 0000000000..bbf8ae1a69
--- /dev/null
+++ b/lib/chef/mixin/powershell_exec.rb
@@ -0,0 +1,128 @@
+#
+# Author:: Stuart Preston (<stuart@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../powershell"
+require_relative "../pwsh"
+
+# The powershell_exec mixin provides in-process access to the PowerShell engine.
+#
+# powershell_exec is initialized with a string that should be set to the script
+# to run and also takes an optional interpreter argument which must be either
+# :powershell (Windows PowerShell which is the default) or :pwsh (PowerShell
+# Core). It will return a Chef::PowerShell object that provides 5 methods:
+#
+# .result - returns a hash representing the results returned by executing the
+# PowerShell script block
+# .verbose - this is an array of string containing any messages written to the
+# PowerShell verbose stream during execution
+# .errors - this is an array of string containing any messages written to the
+# PowerShell error stream during execution
+# .error? - returns true if there were error messages written to the PowerShell
+# error stream during execution
+# .error! - raise Chef::PowerShell::CommandFailed if there was an error
+#
+# Some examples of usage:
+#
+# > powershell_exec("(Get-Item c:\\windows\\system32\\w32time.dll).VersionInfo"
+# ).result["FileVersion"]
+# => "10.0.14393.0 (rs1_release.160715-1616)"
+#
+# > powershell_exec("(get-process ruby).Mainmodule").result["FileName"]
+# => C:\\opscode\\chef\\embedded\\bin\\ruby.exe"
+#
+# > powershell_exec("$a = $true; $a").result
+# => true
+#
+# > powershell_exec("$PSVersionTable", :pwsh).result["PSEdition"]
+# => "Core"
+#
+# > powershell_exec("not-found").errors
+# => ["ObjectNotFound: (not-found:String) [], CommandNotFoundException: The
+# term 'not-found' is not recognized as the name of a cmdlet, function, script
+# file, or operable program. Check the spelling of the name, or if a path was
+# included, verify that the path is correct and try again. (at <ScriptBlock>,
+# <No file>: line 1)"]
+#
+# > powershell_exec("not-found").error?
+# => true
+#
+# > powershell_exec("get-item c:\\notfound -erroraction stop")
+# WIN32OLERuntimeError: (in OLE method `ExecuteScript': )
+# OLE error code:80131501 in System.Management.Automation
+# The running command stopped because the preference variable
+# "ErrorActionPreference" or common parameter is set to Stop: Cannot find
+# path 'C:\notfound' because it does not exist.
+#
+# *Why use this and not powershell_out?* Startup time to invoke the PowerShell
+# engine is much faster (over 7X faster in tests) than writing the PowerShell
+# to disk, shelling out to powershell.exe and retrieving the .stdout or .stderr
+# methods afterwards. Additionally we are able to have a higher fidelity
+# conversation with PowerShell because we are now working with the objects that
+# are returned by the script, rather than having to parse the stdout of
+# powershell.exe to get a result.
+#
+# *How does this work?* In .NET terms, when you run a PowerShell script block
+# through the engine, behind the scenes you get a Collection<PSObject> returned
+# and simply we are serializing this, adding any errors that were generated to
+# a custom JSON string transferred in memory to Ruby. The easiest way to
+# develop for this approach is to imagine that the last thing that happens in
+# your PowerShell script block is "ConvertTo-Json". That's exactly what we are
+# doing here behind the scenes.
+#
+# There are a handful of current limitations with this approach:
+# - Windows UAC elevation is controlled by the token assigned to the account
+# that Ruby.exe is running under.
+# - Terminating errors will result in a WIN32OLERuntimeError and typically are
+# handled as an exception.
+# - There are no return/error codes, as we are not shelling out to
+# powershell.exe but calling a method inline, no errors codes are returned.
+# - There is no settable timeout on powershell_exec method execution.
+# - It is not possible to impersonate another user running powershell, the
+# credentials of the user running Chef Client are used.
+#
+
+class Chef
+ module Mixin
+ module PowershellExec
+ # Run a command under PowerShell via a managed (.NET) API.
+ #
+ # Requires: .NET Framework 4.0 or higher on the target machine.
+ #
+ # @param script [String] script to run
+ # @param interpreter [Symbol] the interpreter type, `:powershell` or `:pwsh`
+ # @return [Chef::PowerShell] output
+ def powershell_exec(script, interpreter = :powershell)
+ case interpreter
+ when :powershell
+ Chef::PowerShell.new(script)
+ when :pwsh
+ Chef::Pwsh.new(script)
+ else
+ raise ArgumentError, "Expected interpreter of :powershell or :pwsh"
+ end
+ end
+
+ # The same as the #powershell_exec method except this will raise
+ # Chef::PowerShell::CommandFailed if the command fails
+ def powershell_exec!(script, interpreter = :powershell)
+ cmd = powershell_exec(script, interpreter)
+ cmd.error!
+ cmd
+ end
+ end
+ end
+end
diff --git a/lib/chef/mixin/powershell_out.rb b/lib/chef/mixin/powershell_out.rb
index 74de85f86f..6d0989f8e0 100644
--- a/lib/chef/mixin/powershell_out.rb
+++ b/lib/chef/mixin/powershell_out.rb
@@ -1,5 +1,5 @@
#--
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,8 +14,8 @@
# 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"
+require_relative "shell_out"
+require_relative "windows_architecture_helper"
class Chef
module Mixin
@@ -28,19 +28,24 @@ class Chef
# can be set to :i386 or :x86_64 to force the windows architecture.
#
# @param script [String] script to run
+ # @param interpreter [Symbol] the interpreter type, `:powershell` or `:pwsh`
# @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
+ interpreter = command_args[1].is_a?(Symbol) ? command_args[1] : :powershell
- run_command_with_os_architecture(script, options)
+ raise ArgumentError, "Expected interpreter of :powershell or :pwsh" unless %i{powershell pwsh}.include?(interpreter)
+
+ run_command_with_os_architecture(script, interpreter, 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 interpreter [Symbol] the interpreter type, `:powershell` or `:pwsh`
# @param options [Hash] options hash
# @return [Mixlib::Shellout] mixlib-shellout object
def powershell_out!(*command_args)
@@ -56,17 +61,18 @@ class Chef
# because chef-client runs as a 32-bit app on 64-bit windows).
#
# @param script [String] script to run
+ # @param interpreter [Symbol] the interpreter type, `:powershell` or `:pwsh`
# @param options [Hash] options hash
# @return [Mixlib::Shellout] mixlib-shellout object
- def run_command_with_os_architecture(script, options)
+ def run_command_with_os_architecture(script, interpreter, options)
options ||= {}
options = options.dup
arch = options.delete(:architecture)
with_os_architecture(nil, architecture: arch) do
shell_out(
- build_powershell_command(script),
- options
+ build_powershell_command(script, interpreter),
+ **options
)
end
end
@@ -74,8 +80,9 @@ class Chef
# 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)
+ # @param interpreter [Symbol] the interpreter type, `:powershell` or `:pwsh`
+ # @return [String] powershell command to execute
+ def build_powershell_command(script, interpreter)
flags = [
# Hides the copyright banner at startup.
"-NoLogo",
@@ -86,12 +93,12 @@ class Chef
# always set the ExecutionPolicy flag
# see http://technet.microsoft.com/en-us/library/ee176961.aspx
"-ExecutionPolicy Unrestricted",
- # Powershell will hang if STDIN is redirected
+ # 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}\""
+ "#{interpreter}.exe #{flags.join(" ")} -Command \"#{script.gsub('"', '\"')}\""
end
end
end
diff --git a/lib/chef/mixin/powershell_type_coercions.rb b/lib/chef/mixin/powershell_type_coercions.rb
index 6159c87948..0e9e9f43d4 100644
--- a/lib/chef/mixin/powershell_type_coercions.rb
+++ b/lib/chef/mixin/powershell_type_coercions.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Edwards (<adamed@chef.io>)
# Author:: Jay Mundrawala (<jdm@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,45 +21,45 @@ class Chef
module Mixin
module PowershellTypeCoercions
- def type_coercions
- @type_coercions ||= {
- Fixnum => { :type => lambda { |x| x.to_s } },
- Float => { :type => lambda { |x| x.to_s } },
- FalseClass => { :type => lambda { |x| "$false" } },
- TrueClass => { :type => lambda { |x| "$true" } },
- Hash => { :type => Proc.new { |x| translate_hash(x) } },
- Array => { :type => Proc.new { |x| translate_array(x) } },
- Chef::Node::ImmutableMash => { :type => Proc.new { |x| translate_hash(x) } },
- Chef::Node::ImmutableArray => { :type => Proc.new { |x| translate_array(x) } },
- }
+ def type_coercion(value)
+ case value
+ when Integer, Float
+ value.to_s
+ when FalseClass
+ "$false"
+ when TrueClass
+ "$true"
+ when Hash, Chef::Node::ImmutableMash
+ translate_hash(value)
+ when Array, Chef::Node::ImmutableArray
+ translate_array(value)
+ end
end
- def translate_type(value)
- translation = type_coercions[value.class]
-
- if translation
- translation[:type].call(value)
- elsif value.respond_to? :to_psobject
+ def psobject_conversion(value)
+ if value.respond_to?(:to_psobject)
"(#{value.to_psobject})"
- else
- safe_string(value.to_s)
end
end
+ def translate_type(value)
+ type_coercion(value) || psobject_conversion(value) || safe_string(value.to_s)
+ end
+
private
def translate_hash(x)
translated = x.inject([]) do |memo, (k, v)|
memo << "#{k}=#{translate_type(v)}"
end
- "@{#{translated.join(';')}}"
+ "@{#{translated.join(";")}}"
end
def translate_array(x)
translated = x.map do |v|
translate_type(v)
end
- "@(#{translated.join(',')})"
+ "@(#{translated.join(",")})"
end
def unsafe?(s)
diff --git a/lib/chef/mixin/properties.rb b/lib/chef/mixin/properties.rb
index 8ff2cc4501..c42e3889b0 100644
--- a/lib/chef/mixin/properties.rb
+++ b/lib/chef/mixin/properties.rb
@@ -1,6 +1,6 @@
-require "chef/delayed_evaluator"
-require "chef/mixin/params_validate"
-require "chef/property"
+require_relative "../delayed_evaluator"
+require_relative "params_validate"
+require_relative "../property"
class Chef
module Mixin
@@ -75,13 +75,15 @@ class Chef
# 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 [String] :description A description of the property.
+ # @option options [String] :introduced The release that introduced this property
# @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] :sensitive `true` if this property could
# contain sensitive information and whose value should be redacted
- # in any resource reporting / auditing output. Defaults to `false`.
+ # in any resource reporting output. Defaults to `false`.
#
# @example Bare property
# property :x
@@ -100,14 +102,14 @@ class Chef
options = options.inject({}) { |memo, (key, value)| memo[key.to_sym] = value; memo }
- options[:instance_variable_name] = :"@#{name}" if !options.has_key?(:instance_variable_name)
+ options[:instance_variable_name] = :"@#{name}" unless options.key?(:instance_variable_name)
options[:name] = name
options[: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)
+ if properties.key?(name)
property = properties[name].derive(**options)
else
property = property_type(**options)
@@ -133,6 +135,8 @@ class Chef
property.emit_dsl
end
+ alias :attribute :property
+
#
# Create a reusable property type that can be used in multiple properties
# in different resources.
@@ -147,6 +151,10 @@ class Chef
Property.derive(**options)
end
+ def deprecated_property_alias(from, to, message)
+ Property.emit_deprecated_alias(from, to, message, self)
+ end
+
#
# Create a lazy value for assignment to a default value.
#
@@ -170,9 +178,6 @@ class Chef
# 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`.
@@ -188,8 +193,8 @@ class Chef
# @return [Array<Property>] All properties in desired state.
#
def state_properties(*names)
- if !names.empty?
- names = names.map { |name| name.to_sym }.uniq
+ unless names.empty?
+ names = names.map(&:to_sym).uniq
local_properties = properties(false)
# Add new properties to the list.
@@ -211,7 +216,7 @@ class Chef
end
end
- properties.values.select { |property| property.desired_state? }
+ properties.values.select(&:desired_state?)
end
#
@@ -237,8 +242,8 @@ class Chef
# @return [Array<Property>] All identity properties.
#
def identity_properties(*names)
- if !names.empty?
- names = names.map { |name| name.to_sym }
+ unless names.empty?
+ names = names.map(&:to_sym)
# Add or change properties that are not part of the identity.
names.each do |name|
@@ -260,11 +265,23 @@ class Chef
end
end
- result = properties.values.select { |property| property.identity? }
- result = [ properties[:name] ] if result.empty?
+ result = properties.values.select(&:identity?)
+ # if there are no other identity properties set, then the name_property becomes the identity, or
+ # failing that we use the actual name.
+ if result.empty?
+ result = name_property ? [ properties[name_property] ] : [ properties[:name] ]
+ end
result
end
+ # Returns the name of the name property. Returns nil if there is no name property.
+ #
+ # @return [Symbol] the name property for this resource
+ def name_property
+ p = properties.find { |n, p| p.name_property? }
+ p ? p.first : nil
+ end
+
def included(other)
other.extend ClassMethods
end
@@ -285,7 +302,8 @@ class Chef
#
def property_is_set?(name)
property = self.class.properties[name.to_sym]
- raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property
+ raise ArgumentError, "Property #{name} is not defined in class #{self}" unless property
+
property.is_set?(self)
end
@@ -298,9 +316,59 @@ class Chef
#
def reset_property(name)
property = self.class.properties[name.to_sym]
- raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property
+ raise ArgumentError, "Property #{name} is not defined in class #{self}" unless property
+
property.reset(self)
end
+
+ #
+ # The description of the property
+ #
+ # @param name [Symbol] The name of the property.
+ # @return [String] The description of the property.
+ def property_description(name)
+ property = self.class.properties[name.to_sym]
+ raise ArgumentError, "Property #{name} is not defined in class #{self}" unless property
+
+ property.description
+ end
+
+ # Copy properties from another property object (resource)
+ #
+ # By default this copies all properties other than the name property (that is required to create the
+ # destination object so it has already been done in advance and this way we do not clobber the name
+ # that was set in that constructor). By default it copies everything, optional arguments can be use
+ # to only select a subset. Or specific excludes can be set (and the default exclude on the name property
+ # can also be overridden). Exclude has priority over include, although the caller is likely better
+ # off doing the set arithmetic themselves for explicitness.
+ #
+ # ```ruby
+ # action :doit do
+ # # use it inside a block
+ # file "/etc/whatever.xyz" do
+ # copy_properties_from new_resource
+ # end
+ #
+ # # or directly call it
+ # r = declare_resource(:file, "etc/whatever.xyz")
+ # r.copy_properties_from(new_resource, :owner, :group, :mode)
+ # end
+ # ```
+ #
+ # @param other [Object] the other object (Chef::Resource) which implements the properties API
+ # @param includes [Array<Symbol>] splat-args list of symbols of the properties to copy.
+ # @param exclude [Array<Symbol>] list of symbols of the properties to exclude.
+ # @return the self object the properties were copied to for method chaining
+ #
+ def copy_properties_from(other, *includes, exclude: [ :name ])
+ includes = other.class.properties.keys if includes.empty?
+ includes -= exclude
+ includes.each do |p|
+ send(p, other.send(p)) if other.property_is_set?(p)
+ end
+ self
+ end
+
end
end
end
diff --git a/lib/chef/mixin/provides.rb b/lib/chef/mixin/provides.rb
index 43a726de8c..d088a72d5d 100644
--- a/lib/chef/mixin/provides.rb
+++ b/lib/chef/mixin/provides.rb
@@ -1,10 +1,10 @@
-require "chef/mixin/descendants_tracker"
+require_relative "descendants_tracker"
class Chef
module Mixin
module Provides
- # TODO no longer needed, remove or deprecate?
+ # @todo no longer needed, remove or deprecate?
include Chef::Mixin::DescendantsTracker
def provides(short_name, opts = {})
@@ -12,7 +12,8 @@ class Chef
end
# Check whether this resource provides the resource_name DSL for the given
- # node. TODO remove this when we stop checking unregistered things.
+ # node.
+ # @todo remove this when we stop checking unregistered things.
# FIXME: yard with @yield
def provides?(node, resource)
raise NotImplementedError, :provides?
diff --git a/lib/chef/mixin/proxified_socket.rb b/lib/chef/mixin/proxified_socket.rb
index 5c9bc3c7d0..3df2def725 100644
--- a/lib/chef/mixin/proxified_socket.rb
+++ b/lib/chef/mixin/proxified_socket.rb
@@ -1,5 +1,5 @@
# Author:: Tyler Ball (<tball@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/lib/chef/mixin/recipe_definition_dsl_core.rb b/lib/chef/mixin/recipe_definition_dsl_core.rb
deleted file mode 100644
index 6a9b12d31a..0000000000
--- a/lib/chef/mixin/recipe_definition_dsl_core.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-#--
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Christopher Walters (<cw@chef.io>)
-# Copyright:: Copyright 2008-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.
-#
-
-###
-# NOTE: This file and constant are here only for backwards compatibility.
-# New code should use Chef::DSL::Recipe instead.
-#
-# This constant (module name) will eventually be deprecated and then removed.
-###
-
-require "chef/mixin/deprecation"
-
-class Chef
- module Mixin
- deprecate_constant(:RecipeDefinitionDSLCore, Chef::DSL::Recipe, <<-EOM)
-Chef::Mixin::RecipeDefinitionDSLCore is deprecated. Use Chef::DSL::Recipe instead.
-EOM
- end
-end
diff --git a/lib/chef/mixin/securable.rb b/lib/chef/mixin/securable.rb
index 55b4e0a6d1..08e92b27b7 100644
--- a/lib/chef/mixin/securable.rb
+++ b/lib/chef/mixin/securable.rb
@@ -1,6 +1,6 @@
#
# Author:: Seth Chisamore (<schisamo@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -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
@@ -42,14 +42,14 @@ class Chef
set_or_return(
:mode,
arg,
- :callbacks => {
+ callbacks: {
"not in valid numeric range" => lambda do |m|
- if m.kind_of?(String)
+ if m.is_a?(String)
m =~ /^0/ || m = "0#{m}"
end
# Windows does not support the sticky or setuid bits
- if Chef::Platform.windows?
+ if ChefUtils.windows?
Integer(m) <= 0777 && Integer(m) >= 0
else
Integer(m) <= 07777 && Integer(m) >= 0
@@ -59,17 +59,14 @@ class Chef
)
end
- #==WindowsMacros
# Defines methods for adding attributes to a chef resource to describe
# Windows file security metadata.
#
# This module is meant to be used to extend a class (instead of
# `include`-ing). A class is automatically extended with this module when
# it includes WindowsSecurableAttributes.
- # --
- # TODO should this be separated into different files?
+ # @todo should this be separated into different files?
module WindowsMacros
- # === rights_attribute
# "meta-method" for dynamically creating rights attributes on resources.
#
# Multiple rights attributes can be declared. This enables resources to
@@ -100,7 +97,7 @@ class Chef
#
# * `:permissions`: Integer of Windows permissions flags, 1..2^32
# or one of `[:full_control, :modify, :read_execute, :read, :write]`
- # * `:principals`: String or Array of Strings represnting usernames on
+ # * `:principals`: String or Array of Strings representing usernames on
# the system.
# * `:applies_to_children` (optional): Boolean
# * `:applies_to_self` (optional): Boolean
@@ -110,19 +107,19 @@ 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_sym)
+ rights = instance_variable_get("@#{name}".to_sym)
unless permissions.nil?
input = {
- :permissions => permissions,
- :principals => principals,
+ permissions: permissions,
+ principals: principals,
}
input.merge!(args_hash) unless args_hash.nil?
- validations = { :permissions => { :required => true },
- :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 ] },
+ validations = { permissions: { required: true },
+ 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 ] },
}
validate(input, validations)
@@ -131,23 +128,23 @@ class Chef
if permission < 0 || permission > 1 << 32
raise ArgumentError, "permissions flags must be positive and <= 32 bits (#{permission})"
end
- elsif !([:full_control, :modify, :read_execute, :read, :write].include?(permission.to_sym))
- raise ArgumentError, "permissions parameter must be :full_control, :modify, :read_execute, :read, :write or an integer representing Windows permission flags"
+ elsif !(%i{full_control modify read_execute read write}.include?(permission.to_sym))
+ raise ArgumentError, "permissions property must be :full_control, :modify, :read_execute, :read, :write or an integer representing Windows permission flags"
end
end
[ principals ].flatten.each do |principal|
- if !principal.is_a?(String)
- raise ArgumentError, "principals parameter must be a string or array of strings representing usernames"
+ unless principal.is_a?(String)
+ raise ArgumentError, "principals property must be a string or array of strings representing usernames"
end
end
if input[:applies_to_children] == false
if input[:applies_to_self] == false
- raise ArgumentError, "'rights' attribute must specify either :applies_to_children or :applies_to_self."
+ raise ArgumentError, "'rights' property must specify either :applies_to_children or :applies_to_self."
end
if input[:one_level_deep] == true
- raise ArgumentError, "'rights' attribute specified :one_level_deep without specifying :applies_to_children."
+ raise ArgumentError, "'rights' property specified :one_level_deep without specifying :applies_to_children."
end
end
rights ||= []
@@ -162,7 +159,6 @@ class Chef
end
end
- #==WindowsSecurableAttributes
# Defines #inherits to describe Windows file security ACLs on the
# including class
module WindowsSecurableAttributes
@@ -171,19 +167,19 @@ class Chef
set_or_return(
:inherits,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ kind_of: [ TrueClass, FalseClass ]
)
end
end
- if RUBY_PLATFORM =~ /mswin|mingw|windows/
+ if RUBY_PLATFORM.match?(/mswin|mingw|windows/)
include WindowsSecurableAttributes
end
# Callback that fires when included; will extend the including class
# with WindowsMacros and define #rights and #deny_rights on it.
def self.included(including_class)
- if RUBY_PLATFORM =~ /mswin|mingw|windows/
+ if RUBY_PLATFORM.match?(/mswin|mingw|windows/)
including_class.extend(WindowsMacros)
# create a default 'rights' attribute
including_class.rights_attribute(:rights)
diff --git a/lib/chef/mixin/shell_out.rb b/lib/chef/mixin/shell_out.rb
index a258a91075..222cd73a73 100644
--- a/lib/chef/mixin/shell_out.rb
+++ b/lib/chef/mixin/shell_out.rb
@@ -1,6 +1,6 @@
#--
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,122 +15,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require "mixlib/shellout"
+require "mixlib/shellout/helper" unless defined?(Mixlib::ShellOut::Helper)
+require_relative "chef_utils_wiring" unless defined?(Chef::Mixin::ChefUtilsWiring)
class Chef
module Mixin
module ShellOut
-
- # shell_out! runs a command on the system and will raise an error if the command fails, which is what you want
- # for debugging, shell_out and shell_out! both will display command output to the tty when the log level is debug
- # Generally speaking, 'extend Chef::Mixin::ShellOut' in your recipes and include 'Chef::Mixin::ShellOut' in your LWRPs
- # You can also call Mixlib::Shellout.new directly, but you lose all of the above functionality
-
- # we use 'en_US.UTF-8' by default because we parse localized strings in English as an API and
- # generally must support UTF-8 unicode.
- def shell_out(*args, **options)
- options = options.dup
- env_key = options.has_key?(:env) ? :env : :environment
- options[env_key] = {
- "LC_ALL" => Chef::Config[:internal_locale],
- "LANGUAGE" => Chef::Config[:internal_locale],
- "LANG" => Chef::Config[:internal_locale],
- }.update(options[env_key] || {})
- shell_out_command(*args, **options)
- end
-
- # call shell_out (using en_US.UTF-8) and raise errors
- def shell_out!(*command_args)
- cmd = shell_out(*command_args)
- cmd.error!
- cmd
- end
-
- def shell_out_with_systems_locale(*command_args)
- shell_out_command(*command_args)
- end
-
- def shell_out_with_systems_locale!(*command_args)
- cmd = shell_out_with_systems_locale(*command_args)
- cmd.error!
- cmd
- end
-
- DEPRECATED_OPTIONS =
- [ [:command_log_level, :log_level],
- [:command_log_prepend, :log_tag] ]
-
- # CHEF-3090: Deprecate command_log_level and command_log_prepend
- # Patterned after https://github.com/opscode/chef/commit/e1509990b559984b43e428d4d801c394e970f432
- def run_command_compatible_options(command_args)
- return command_args unless command_args.last.is_a?(Hash)
-
- my_command_args = command_args.dup
- my_options = my_command_args.last
-
- DEPRECATED_OPTIONS.each do |old_option, new_option|
- # Edge case: someone specifies :command_log_level and 'command_log_level' in the option hash
- next unless value = my_options.delete(old_option) || my_options.delete(old_option.to_s)
- deprecate_option old_option, new_option
- my_options[new_option] = value
- end
-
- return my_command_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)
- clean_array(*args).join(" ")
- end
-
- # Helper for sublcasses to reject nil and empty strings out of an array. It allows
- # using the array form of shell_out (which avoids the need to surround arguments with
- # quote marks to deal with shells).
- #
- # Usage:
- # shell_out!(*clean_array("useradd", universal_options, useradd_options, new_resource.username))
- #
- # universal_options and useradd_options can be nil, empty array, empty string, strings or arrays
- # and the result makes sense.
- #
- # keeping this separate from shell_out!() makes it a bit easier to write expectations against the
- # shell_out args and be able to omit nils and such in the tests (and to test that the nils are
- # being rejected correctly).
- #
- # @param args [String] variable number of string arguments
- # @return [Array] array of strings with nil and null string rejection
- def clean_array(*args)
- args.flatten.reject { |i| i.nil? || i == "" }.map(&:to_s)
- end
-
- private
-
- def shell_out_command(*command_args)
- cmd = Mixlib::ShellOut.new(*run_command_compatible_options(command_args))
- cmd.live_stream ||= io_for_live_stream
- cmd.run_command
- cmd
- end
-
- def deprecate_option(old_option, new_option)
- Chef.log_deprecation "DEPRECATION: Chef::Mixin::ShellOut option :#{old_option} is deprecated. Use :#{new_option}"
- end
-
- def io_for_live_stream
- if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.debug?
- STDOUT
- else
- nil
- end
- end
+ include Mixlib::ShellOut::Helper
+ include Chef::Mixin::ChefUtilsWiring
end
end
end
-
-# Break circular dep
-require "chef/config"
diff --git a/lib/chef/mixin/subclass_directive.rb b/lib/chef/mixin/subclass_directive.rb
index 397a37c6b8..2b0831233a 100644
--- a/lib/chef/mixin/subclass_directive.rb
+++ b/lib/chef/mixin/subclass_directive.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/lib/chef/mixin/template.rb b/lib/chef/mixin/template.rb
index b10e036c4e..dcb728f964 100644
--- a/lib/chef/mixin/template.rb
+++ b/lib/chef/mixin/template.rb
@@ -1,6 +1,6 @@
#--
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,14 +16,13 @@
# limitations under the License.
#
-require "tempfile"
-require "erubis"
+autoload :Tempfile, "tempfile"
+autoload :Erubis, "erubis"
class Chef
module Mixin
module Template
- # == ChefContext
# ChefContext was previously used to mix behavior into Erubis::Context so
# that it would be available to templates. This behavior has now moved to
# TemplateContext, but this module is still mixed in to the
@@ -32,7 +31,6 @@ class Chef
module ChefContext
end
- # == TemplateContext
# TemplateContext is the base context class for all templates in Chef. It
# defines user-facing extensions to the base Erubis::Context to provide
# enhanced features. Individual instances of TemplateContext can be
@@ -72,7 +70,7 @@ class Chef
# @return [String] recipe path
attr_reader :recipe_path
- # line in the recipe containing the template reosurce, e.g.:
+ # line in the recipe containing the template resource, e.g.:
# 2
#
# @return [String] recipe line
@@ -104,6 +102,7 @@ class Chef
# by the bare `node` everywhere.
def node
return @node if @node
+
raise "Could not find a value for node. If you are explicitly setting variables in a template, " +
"include a node variable if you plan to use it."
end
@@ -140,11 +139,11 @@ class Chef
partial_context._extend_modules(@_extension_modules)
template_location = @template_finder.find(partial_name, options)
- _render_template(IO.binread(template_location), partial_context)
+ _render_template(IO.binread(template_location), partial_context, filename: template_location)
end
def render_template(template_location)
- _render_template(IO.binread(template_location), self)
+ _render_template(IO.binread(template_location), self, filename: template_location)
end
def render_template_from_string(template)
@@ -155,12 +154,13 @@ class Chef
# INTERNAL PUBLIC API
###
- def _render_template(template, context)
+ def _render_template(template, context, options = {})
begin
- eruby = Erubis::Eruby.new(template)
+ # eruby = Erubis::Eruby.new(template, options)
+ eruby = Erubis::Eruby.new(template, options)
output = eruby.evaluate(context)
rescue Object => e
- raise TemplateError.new(e, template, context)
+ raise TemplateError.new(e, template, context, options)
end
# CHEF-4399
@@ -175,7 +175,7 @@ class Chef
# potential issues for the applications that will consume
# this template.
- if Chef::Platform.windows?
+ if ChefUtils.windows?
output = output.gsub(/\r?\n/, "\r\n")
end
@@ -184,7 +184,7 @@ class Chef
def _extend_modules(module_names)
module_names.each do |mod|
- context_methods = [:node, :render, :render_template, :render_template_from_string]
+ context_methods = %i{node render render_template render_template_from_string}
context_methods.each do |core_method|
if mod.method_defined?(core_method) || mod.private_method_defined?(core_method)
Chef::Log.warn("Core template method `#{core_method}' overridden by extension module #{mod}")
@@ -204,7 +204,7 @@ class Chef
all_ivars.delete(:@_extension_modules)
all_ivars.inject({}) do |ivar_map, ivar_symbol_name|
value = instance_variable_get(ivar_symbol_name)
- name_without_at = ivar_symbol_name.to_s[1..-1].to_sym
+ name_without_at = ivar_symbol_name.to_s[1..].to_sym
ivar_map[name_without_at] = value
ivar_map
end
@@ -212,11 +212,12 @@ class Chef
end
class TemplateError < RuntimeError
- attr_reader :original_exception, :context
+ attr_reader :original_exception, :context, :options
+
SOURCE_CONTEXT_WINDOW = 2
- def initialize(original_exception, template, context)
- @original_exception, @template, @context = original_exception, template, context
+ def initialize(original_exception, template, context, options)
+ @original_exception, @template, @context, @options = original_exception, template, context, options
end
def message
@@ -224,7 +225,11 @@ class Chef
end
def line_number
- @line_number ||= $1.to_i if original_exception.backtrace.find { |line| line =~ /\(erubis\):(\d+)/ }
+ @line_number ||= if options[:filename]
+ $1.to_i if original_exception.backtrace.find { |line| line =~ /#{Regexp.escape(options[:filename])}:(\d+)/ }
+ else
+ $1.to_i if original_exception.backtrace.find { |line| line =~ /\(erubis\):(\d+)/ }
+ end
end
def source_location
diff --git a/lib/chef/mixin/unformatter.rb b/lib/chef/mixin/unformatter.rb
index 7dad56b270..c233549b17 100644
--- a/lib/chef/mixin/unformatter.rb
+++ b/lib/chef/mixin/unformatter.rb
@@ -1,6 +1,6 @@
#
# Author:: Jay Mundrawala (<jdm@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,10 +21,10 @@ class Chef
module Unformatter
def write(message)
- data = message.match(/(\[.+?\] )?([\w]+):(.*)$/)
- self.send(data[2].downcase.chomp.to_sym, data[3].strip)
+ data = message.match(/(\[.+?\] )?(\w+):(.*)$/)
+ send(data[2].downcase.chomp.to_sym, data[3].strip)
rescue NoMethodError
- self.send(:info, message)
+ send(:info, message)
end
end
diff --git a/lib/chef/mixin/uris.rb b/lib/chef/mixin/uris.rb
index 7dc04d662b..13c4ee29b3 100644
--- a/lib/chef/mixin/uris.rb
+++ b/lib/chef/mixin/uris.rb
@@ -1,6 +1,6 @@
#
# Author:: Jay Mundrawala (<jdm@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,8 +16,10 @@
# limitations under the License.
#
-require "uri"
-require "addressable/uri"
+autoload :URI, "uri"
+module Addressable
+ autoload :URI, "addressable/uri"
+end
class Chef
module Mixin
@@ -31,12 +33,10 @@ class Chef
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(Addressable::URI.encode(source))
- end
+ URI.parse(source)
+ rescue URI::InvalidURIError
+ Chef::Log.warn("#{source} was an invalid URI. Trying to escape invalid characters")
+ URI.parse(Addressable::URI.encode(source))
end
end
diff --git a/lib/chef/mixin/user_context.rb b/lib/chef/mixin/user_context.rb
new file mode 100644
index 0000000000..d40d233f35
--- /dev/null
+++ b/lib/chef/mixin/user_context.rb
@@ -0,0 +1,55 @@
+#
+# Author:: Adam Edwards (<adamed@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../util/windows/logon_session" if ChefUtils.windows?
+
+class Chef
+ module Mixin
+ module UserContext
+
+ # valid values for authentication => :remote, :local
+ # When authentication = :local, we use the credentials to create a logon session against the local system, and then try to access the files.
+ # When authentication = :remote, we continue with the current user but pass the provided credentials to the remote system.
+ def with_user_context(user, password, domain = nil, authentication = :remote, &block)
+ unless ChefUtils.windows?
+ raise Exceptions::UnsupportedPlatform, "User context impersonation is supported only on the Windows platform"
+ end
+
+ unless block_given?
+ raise ArgumentError, "You must supply a block to `with_user_context`"
+ end
+
+ logon_session = nil
+
+ begin
+ if user
+ logon_session = Chef::Util::Windows::LogonSession.new(user, password, domain, authentication)
+ logon_session.open
+ logon_session.set_user_context
+ end
+ yield
+ ensure
+ logon_session.close if logon_session
+ end
+ end
+
+ protected(:with_user_context)
+
+ end
+ end
+end
diff --git a/lib/chef/mixin/versioned_api.rb b/lib/chef/mixin/versioned_api.rb
new file mode 100644
index 0000000000..b627e0210c
--- /dev/null
+++ b/lib/chef/mixin/versioned_api.rb
@@ -0,0 +1,83 @@
+#--
+# Copyright:: Copyright (c) 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 VersionedAPI
+
+ def minimum_api_version(version = nil)
+ if version
+ @minimum_api_version = version
+ else
+ @minimum_api_version
+ end
+ end
+
+ end
+
+ module VersionedAPIFactory
+
+ def versioned_interfaces
+ @versioned_interfaces ||= []
+ end
+
+ def add_versioned_api_class(klass)
+ versioned_interfaces << klass
+ end
+
+ def versioned_api_class
+ get_class_for(:max_server_version)
+ end
+
+ def get_class_for(type)
+ versioned_interfaces.select do |klass|
+ version = klass.send(:minimum_api_version)
+ # min and max versions will be nil if we've not made a request to the server yet,
+ # in which case we'll just start with the highest version and see what happens
+ ServerAPIVersions.instance.min_server_version.nil? || (version >= ServerAPIVersions.instance.min_server_version && version <= ServerAPIVersions.instance.send(type))
+ end
+ .max_by { |a| a.send(:minimum_api_version) }
+ end
+
+ def def_versioned_delegator(method)
+ line_no = __LINE__; str = %{
+ def self.#{method}(*args, &block)
+ versioned_api_class.__send__(:#{method}, *args, &block)
+ end
+ }
+ module_eval(str, __FILE__, line_no)
+ end
+
+ # When teeing up an HTTP request, we need to be able to ask which API version we should use.
+ # Something in Net::HTTP seems to expect to strip headers, so we return this as a string.
+ def best_request_version
+ klass = get_class_for(:max_server_version)
+ klass.minimum_api_version.to_s
+ end
+
+ def possible_requests
+ versioned_interfaces.length
+ end
+
+ def new(*args)
+ object = versioned_api_class.allocate
+ object.send(:initialize, *args)
+ object
+ end
+ end
+ end
+end
diff --git a/lib/chef/mixin/which.rb b/lib/chef/mixin/which.rb
index 63c84883d5..a8048962ee 100644
--- a/lib/chef/mixin/which.rb
+++ b/lib/chef/mixin/which.rb
@@ -1,6 +1,6 @@
#--
# Author:: Lamont Granquist <lamont@chef.io>
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,22 +15,24 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+require "chef-utils/dsl/which" unless defined?(ChefUtils::DSL::Which)
+require "chef-utils/dsl/default_paths" unless defined?(ChefUtils::DSL::DefaultPaths)
+require_relative "chef_utils_wiring" unless defined?(Chef::Mixin::ChefUtilsWiring)
+
class Chef
module Mixin
module Which
- def which(cmd, opts = {})
- extra_path =
- if opts[:extra_path].nil?
- [ "/bin", "/usr/bin", "/sbin", "/usr/sbin" ]
- else
- [ opts[:extra_path] ].flatten
- end
- paths = ENV["PATH"].split(File::PATH_SEPARATOR) + extra_path
- paths.each do |path|
- filename = File.join(path, cmd)
- return filename if File.executable?(Chef.path_to(filename))
- end
- false
+ include ChefUtils::DSL::Which
+ include ChefUtils::DSL::DefaultPaths
+ include ChefUtilsWiring
+
+ private
+
+ # we dep-inject default paths into this API for historical reasons
+ #
+ # @api private
+ def __extra_path
+ __default_paths
end
end
end
diff --git a/lib/chef/mixin/why_run.rb b/lib/chef/mixin/why_run.rb
index ea62527bdd..efe327168e 100644
--- a/lib/chef/mixin/why_run.rb
+++ b/lib/chef/mixin/why_run.rb
@@ -1,7 +1,7 @@
#
# Author:: Dan DeLeo ( <dan@chef.io> )
# Author:: Marc Paradise ( <marc@chef.io> )
-# Copyright:: Copyright 2012-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,7 +21,6 @@ class Chef
module Mixin
module WhyRun
- # ==ConvergeActions
# ConvergeActions implements the logic for why run. A ConvergeActions
# object wraps a collection of actions, which consist of a descriptive
# string and a block/Proc. Actions are executed by calling #converge!
@@ -60,7 +59,6 @@ class Chef
end
end
- # == ResourceRequirements
# ResourceRequirements provides a framework for making assertions about
# the host system's state. It also provides a mechanism for making
# assumptions about what the system's state might have been when running
@@ -182,7 +180,7 @@ class Chef
# will be allowed to continue in both whyrun and non-whyrun
# mode
#
- # With a service resource that requires /etc/init.d/service-name to exist:
+ # @example With a service resource that requires /etc/init.d/service-name to exist:
# # in a provider
# assert(:start, :restart) do |a|
# a.assertion { ::File.exist?("/etc/init.d/service-name") }
@@ -264,8 +262,8 @@ class Chef
# Define a new Assertion.
#
# Takes a list of action names for which the assertion should be made.
- # ==== Examples:
- # A File provider that requires the parent directory to exist:
+ #
+ # @example A File provider that requires the parent directory to exist:
#
# assert(:create, :create_if_missing) do |a|
# parent_dir = File.basename(@new_resource.path)
@@ -275,7 +273,7 @@ class Chef
# a.why_run("assuming parent directory #{parent_dir} would have been previously created"
# end
#
- # A service provider that requires the init script to exist:
+ # @example A service provider that requires the init script to exist:
#
# assert(:start, :restart) do |a|
# a.assertion { ::File.exist?(@new_resource.init_script) }
@@ -286,17 +284,14 @@ class Chef
# end
# end
#
- # A File provider that will error out if you don't have permissions do
- # delete the file, *even in why run mode*:
- #
+ # @example A File provider that will error out if you don't have permissions do delete the file, *even in why run mode*:
# assert(:delete) do |a|
# a.assertion { ::File.writable?(@new_resource.path) }
# a.failure_message(Exceptions::InsufficientPrivileges,
# "You don't have sufficient privileges to delete #{@new_resource.path}")
# end
#
- # A Template provider that will prevent action execution but continue the run in
- # whyrun mode if the template source is not available.
+ # @example A Template provider that will prevent action execution but continue the run in whyrun mode if the template source is not available.
# assert(:create, :create_if_missing) do |a|
# a.assertion { File::exist?(@new_resource.source) }
# a.failure_message Chef::Exceptions::TemplateError, "Template #{@new_resource.source} could not be found exist."
diff --git a/lib/chef/mixin/wide_string.rb b/lib/chef/mixin/wide_string.rb
index 4342ef1650..dbc9939ec2 100644
--- a/lib/chef/mixin/wide_string.rb
+++ b/lib/chef/mixin/wide_string.rb
@@ -1,6 +1,6 @@
#
# Author:: Jay Mundrawala(<jdm@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -31,40 +31,22 @@ class Chef
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"
+ ustring = (ustring + "").force_encoding("UTF-8")
# ensure we have the double-null termination Windows Wide likes
- ustring = ustring + "\000\000" if ustring.length == 0 || ustring[-1].chr != "\000"
+ ustring += "\000\000" if ustring.length == 0 || 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
+ ustring.encode("UTF-16LE")
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)
+ wstring = wstring.force_encoding("UTF-16LE")
- # 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
+ # encode it all as UTF-8 and remove trailing CRLF and NULL characters
+ wstring.encode("UTF-8").strip
end
end
diff --git a/lib/chef/mixin/windows_architecture_helper.rb b/lib/chef/mixin/windows_architecture_helper.rb
index 49252af484..a27a9c7932 100644
--- a/lib/chef/mixin/windows_architecture_helper.rb
+++ b/lib/chef/mixin/windows_architecture_helper.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Edwards (<adamed@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,10 +16,10 @@
# limitations under the License.
#
-require "chef/exceptions"
-require "chef/platform/query_helpers"
-require "chef/win32/process" if Chef::Platform.windows?
-require "chef/win32/system" if Chef::Platform.windows?
+require_relative "../exceptions"
+require_relative "../platform/query_helpers"
+require_relative "../win32/process" if ChefUtils.windows?
+require_relative "../win32/system" if ChefUtils.windows?
class Chef
module Mixin
@@ -49,8 +49,8 @@ class Chef
node ||= begin
os_arch = ENV["PROCESSOR_ARCHITEW6432"] ||
ENV["PROCESSOR_ARCHITECTURE"]
- Hash.new.tap do |n|
- n[:kernel] = Hash.new
+ {}.tap do |n|
+ n[:kernel] = {}
n[:kernel][:machine] = os_arch == "AMD64" ? :x86_64 : :i386
end
end
@@ -74,22 +74,22 @@ class Chef
def node_supports_windows_architecture?(node, desired_architecture)
assert_valid_windows_architecture!(desired_architecture)
- return ( node_windows_architecture(node) == :x86_64 ) || ( desired_architecture == :i386 )
+ ( node_windows_architecture(node) == :x86_64 ) || ( desired_architecture == :i386 )
end
def valid_windows_architecture?(architecture)
- return ( architecture == :x86_64 ) || ( architecture == :i386 )
+ ( architecture == :x86_64 ) || ( architecture == :i386 )
end
def assert_valid_windows_architecture!(architecture)
- if !valid_windows_architecture?(architecture)
+ unless valid_windows_architecture?(architecture)
raise Chef::Exceptions::Win32ArchitectureIncorrect,
- "The specified architecture was not valid. It must be one of :i386 or :x86_64"
+ "The specified architecture was not valid. It must be one of :i386 or :x86_64"
end
end
def is_i386_process_on_x86_64_windows?
- if Chef::Platform.windows?
+ if ChefUtils.windows?
Chef::ReservedNames::Win32::Process.is_wow64_process
else
false
@@ -97,13 +97,13 @@ class Chef
end
def disable_wow64_file_redirection( node )
- if ( node_windows_architecture(node) == :x86_64) && ::Chef::Platform.windows?
+ if ( node_windows_architecture(node) == :x86_64) && ::ChefUtils.windows?
Chef::ReservedNames::Win32::System.wow64_disable_wow64_fs_redirection
end
end
def restore_wow64_file_redirection( node, original_redirection_state )
- if (node_windows_architecture(node) == :x86_64) && ::Chef::Platform.windows?
+ if (node_windows_architecture(node) == :x86_64) && ::ChefUtils.windows?
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 65b4b33cf0..dcf8dbddf3 100644
--- a/lib/chef/mixin/windows_env_helper.rb
+++ b/lib/chef/mixin/windows_env_helper.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Edwards (<adamed@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,23 +16,23 @@
# limitations under the License.
#
-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?
+require_relative "../exceptions"
+require_relative "wide_string"
+require_relative "../platform/query_helpers"
+require_relative "../win32/error" if ChefUtils.windows?
+require_relative "../win32/api/system" if ChefUtils.windows?
+require_relative "../win32/api/unicode" if ChefUtils.windows?
class Chef
module Mixin
module WindowsEnvHelper
include Chef::Mixin::WideString
- if Chef::Platform.windows?
+ if ChefUtils.windows?
include Chef::ReservedNames::Win32::API::System
end
- #see: http://msdn.microsoft.com/en-us/library/ms682653%28VS.85%29.aspx
+ # see: http://msdn.microsoft.com/en-us/library/ms682653%28VS.85%29.aspx
HWND_BROADCAST = 0xffff
WM_SETTINGCHANGE = 0x001A
SMTO_BLOCK = 0x0001
@@ -46,8 +46,8 @@ class Chef
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")
+ 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
diff --git a/lib/chef/mixin/xml_escape.rb b/lib/chef/mixin/xml_escape.rb
index f9a41b9fd0..6f2f369e4f 100644
--- a/lib/chef/mixin/xml_escape.rb
+++ b/lib/chef/mixin/xml_escape.rb
@@ -1,6 +1,6 @@
#--
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# Copyright:: Copyright 2005-2016, Sam Ruby
# License:: Apache License, Version 2.0
#
@@ -46,7 +46,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
-require "chef/log"
+require_relative "../log"
begin
require "fast_xs"
@@ -73,8 +73,8 @@ class Chef
137 => 8240, # per mille sign
138 => 352, # latin capital letter s with caron
139 => 8249, # single left-pointing angle quotation mark
- 140 => 338, # latin capital ligature oe
- 142 => 381, # latin capital letter z with caron
+ 140 => 338, # latin capital ligature oe
+ 142 => 381, # latin capital letter z with caron
145 => 8216, # left single quotation mark
146 => 8217, # right single quotation mark
147 => 8220, # left double quotation mark
@@ -86,28 +86,26 @@ class Chef
153 => 8482, # trade mark sign
154 => 353, # latin small letter s with caron
155 => 8250, # single right-pointing angle quotation mark
- 156 => 339, # latin small ligature oe
- 158 => 382, # latin small letter z with caron
- 159 => 376 # latin capital letter y with diaeresis
- }
+ 156 => 339, # latin small ligature oe
+ 158 => 382, # latin small letter z with caron
+ 159 => 376, # latin capital letter y with diaeresis
+ }.freeze
# http://www.w3.org/TR/REC-xml/#dt-chardata
PREDEFINED = {
38 => "&amp;", # ampersand
60 => "&lt;", # left angle bracket
- 62 => "&gt;" # right angle bracket
- }
+ 62 => "&gt;", # right angle bracket
+ }.freeze
# http://www.w3.org/TR/REC-xml/#charsets
VALID = [[0x9, 0xA, 0xD], (0x20..0xD7FF),
- (0xE000..0xFFFD), (0x10000..0x10FFFF)]
+ (0xE000..0xFFFD), (0x10000..0x10FFFF)].freeze
def xml_escape(unescaped_str)
- begin
- unescaped_str.unpack("U*").map { |char| xml_escape_char!(char) }.join
- rescue
- unescaped_str.unpack("C*").map { |char| xml_escape_char!(char) }.join
- end
+ unescaped_str.unpack("U*").map { |char| xml_escape_char!(char) }.join
+ rescue
+ unescaped_str.unpack("C*").map { |char| xml_escape_char!(char) }.join
end
private
diff --git a/lib/chef/mixins.rb b/lib/chef/mixins.rb
index b980e81287..7d8ef849fe 100644
--- a/lib/chef/mixins.rb
+++ b/lib/chef/mixins.rb
@@ -1,13 +1,13 @@
-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_relative "mixin/shell_out"
+require_relative "mixin/checksum"
+require_relative "mixin/convert_to_class_name"
+require_relative "mixin/create_path"
+require_relative "mixin/deep_merge"
+require_relative "mixin/enforce_ownership_and_permissions"
+require_relative "mixin/from_file"
+require_relative "mixin/params_validate"
+require_relative "mixin/default_paths"
+require_relative "mixin/path_sanity"
+require_relative "mixin/template"
+require_relative "mixin/securable"
+require_relative "mixin/xml_escape"
diff --git a/lib/chef/monkey_patches/net-ssh-multi.rb b/lib/chef/monkey_patches/net-ssh-multi.rb
deleted file mode 100644
index 7b7b1bbf7f..0000000000
--- a/lib/chef/monkey_patches/net-ssh-multi.rb
+++ /dev/null
@@ -1,141 +0,0 @@
-#
-# Author:: Serdar Sutay (<serdar@chef.io>)
-# 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.
-#
-
-# == net-ssh-multi gem patch for concurrency
-# net-ssh-multi gem has 2 bugs associated with the use of
-# :concurrent_connections option.
-# 1-) There is a race condition while fetching the next_session when
-# :concurrent_connections are set. @open_connections is being
-# incremented by the connection thread and sometimes
-# realize_pending_connections!() method can create more than required
-# connection threads before the @open_connections is set by the
-# previously created threads.
-# 2-) When :concurrent_connections is set, server classes are setup
-# with PendingConnection objects that always return true to busy?
-# calls. If a connection fails when :concurrent_connections is set,
-# server ends up returning true to all busy? calls since the session
-# object is not replaced. Due to this, main event loop (process()
-# function) never gets terminated.
-#
-# See: https://github.com/net-ssh/net-ssh-multi/pull/4
-
-require "net/ssh/multi/version"
-
-if Net::SSH::Multi::Version::STRING == "1.1.0" || Net::SSH::Multi::Version::STRING == "1.2.0"
-
- require "net/ssh/multi"
-
- module Net
- module SSH
- module Multi
- class Server
-
- # Make sure that server returns false if the ssh connection
- # has failed.
- def busy?(include_invisible = false)
- !failed? && session && session.busy?(include_invisible)
- end
-
- end
-
- class Session
- def next_session(server, force = false) #:nodoc:
- # don't retry a failed attempt
- return nil if server.failed?
-
- @session_mutex.synchronize do
- if !force && concurrent_connections && concurrent_connections <= open_connections
- connection = PendingConnection.new(server)
- @pending_sessions << connection
- return connection
- end
-
- # ===== PATCH START
- # Only increment the open_connections count if the connection
- # is not being forced. Incase of a force, it will already be
- # incremented.
- if !force
- @open_connections += 1
- end
- # ===== PATCH END
- end
-
- begin
- server.new_session
-
- # I don't understand why this should be necessary--StandardError is a
- # subclass of Exception, after all--but without explicitly rescuing
- # StandardError, things like Errno::* and SocketError don't get caught
- # here!
- rescue Exception, StandardError => e
- server.fail!
- @session_mutex.synchronize { @open_connections -= 1 }
-
- case on_error
- when :ignore then
- # do nothing
- when :warn then
- warn("error connecting to #{server}: #{e.class} (#{e.message})")
- when Proc then
- go = catch(:go) { on_error.call(server); nil }
- case go
- when nil, :ignore then # nothing
- when :retry then retry
- when :raise then raise
- else warn "unknown 'go' command: #{go.inspect}"
- end
- else
- raise
- end
-
- return nil
- end
- end
-
- def realize_pending_connections! #:nodoc:
- return unless concurrent_connections
-
- server_list.each do |server|
- server.close if !server.busy?(true)
- server.update_session!
- end
-
- @connect_threads.delete_if { |t| !t.alive? }
-
- count = concurrent_connections ? (concurrent_connections - open_connections) : @pending_sessions.length
- count.times do
- session = @pending_sessions.pop
- break unless session
- # ===== PATCH START
- # Increment the open_connections count here to prevent
- # creation of connection thread again before that is
- # incremented by the thread.
- @session_mutex.synchronize { @open_connections += 1 }
- # ===== PATCH END
- @connect_threads << Thread.new do
- session.replace_with(next_session(session.server, true))
- end
- end
- end
-
- end
- end
- end
- end
-
-end
diff --git a/lib/chef/monkey_patches/net_http.rb b/lib/chef/monkey_patches/net_http.rb
deleted file mode 100644
index c1cb87bffd..0000000000
--- a/lib/chef/monkey_patches/net_http.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-
-# Module gets mixed in to Net::HTTP exception classes so we can attach our
-# RESTRequest object to them and get the request parameters back out later.
-module ChefNetHTTPExceptionExtensions
- attr_accessor :chef_rest_request
-end
-
-require "net/http"
-module Net
- class HTTPError
- include ChefNetHTTPExceptionExtensions
- end
- class HTTPRetriableError
- include ChefNetHTTPExceptionExtensions
- end
- class HTTPServerException
- include ChefNetHTTPExceptionExtensions
- end
- class HTTPFatalError
- include ChefNetHTTPExceptionExtensions
- end
-end
-
-if Net::HTTP.instance_methods.map { |m| m.to_s }.include?("proxy_uri")
- begin
- # Ruby 2.0 changes the way proxy support is implemented in Net::HTTP.
- # The implementation does not work correctly with IPv6 literals because it
- # concatenates the address into a URI without adding square brackets for
- # IPv6 addresses.
- #
- # If the bug is present, a call to Net::HTTP#proxy_uri when the host is an
- # IPv6 address will fail by creating an invalid URI, like so:
- #
- # ruby -r'net/http' -e 'Net::HTTP.new("::1", 80).proxy_uri'
- # /Users/ddeleo/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/uri/generic.rb:214:in `initialize': the scheme http does not accept registry part: ::1:80 (or bad hostname?) (URI::InvalidURIError)
- # from /Users/ddeleo/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/uri/http.rb:84:in `initialize'
- # from /Users/ddeleo/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/uri/common.rb:214:in `new'
- # from /Users/ddeleo/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/uri/common.rb:214:in `parse'
- # from /Users/ddeleo/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/uri/common.rb:747:in `parse'
- # from /Users/ddeleo/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/uri/common.rb:994:in `URI'
- # from /Users/ddeleo/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/net/http.rb:1027:in `proxy_uri'
- # from -e:1:in `<main>'
- #
- # https://bugs.ruby-lang.org/issues/9129
- #
- # NOTE: This should be fixed in Ruby 2.2.0, and backported to Ruby 2.0 and
- # 2.1 (not yet released so the version/patchlevel required isn't known
- # yet).
- Net::HTTP.new("::1", 80).proxy_uri
- rescue URI::InvalidURIError
- class Net::HTTP
-
- def proxy_uri # :nodoc:
- ipv6_safe_addr = address.to_s.include?(":") ? "[#{address}]" : address
- @proxy_uri ||= URI("http://#{ipv6_safe_addr}:#{port}").find_proxy
- end
-
- end
- end
-end
diff --git a/lib/chef/monkey_patches/webrick-utils.rb b/lib/chef/monkey_patches/webrick-utils.rb
index ccca5825e2..6e3748f308 100644
--- a/lib/chef/monkey_patches/webrick-utils.rb
+++ b/lib/chef/monkey_patches/webrick-utils.rb
@@ -24,27 +24,29 @@ module WEBrick
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
+ Socket::AF_UNSPEC, # address family
+ Socket::SOCK_STREAM, # socket type
+ 0, # protocol
+ Socket::AI_PASSIVE) # flag
last_error = nil
sockets = []
res.each do |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
+
+ 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
+
+ sockets
end
module_function :create_listeners
end
diff --git a/lib/chef/monkey_patches/win32/registry.rb b/lib/chef/monkey_patches/win32/registry.rb
index a08d67becf..dbcbf0ee1e 100644
--- a/lib/chef/monkey_patches/win32/registry.rb
+++ b/lib/chef/monkey_patches/win32/registry.rb
@@ -1,5 +1,5 @@
#
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,9 +15,9 @@
# limitations under the License.
#
-require "chef/win32/api/registry"
-require "chef/win32/unicode"
-require "win32/registry"
+require_relative "../../win32/api/registry"
+require_relative "../../win32/unicode"
+require "win32/registry" unless defined?(Win32::Registry)
module Win32
class Registry
@@ -56,31 +56,5 @@ module Win32
end
end
-
- if RUBY_VERSION =~ /^2\.1/
- # ::Win32::Registry#write does not correctly handle data in Ruby 2.1
- # This bug is _reportedly_ resolved in Ruby 2.1.7 and 2.2.3
- # but fails in appveyor on 2.1.8 unless we keep applying this monkeypatch
- # 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 82816e887f..b9611feaf2 100644
--- a/lib/chef/monologger.rb
+++ b/lib/chef/monologger.rb
@@ -1,88 +1,4 @@
-require "logger"
-require "pp"
+require "mixlib/log/logger"
+require "pp" unless defined?(PP)
-#== MonoLogger
-# A subclass of Ruby's stdlib Logger with all the mutex and logrotation stuff
-# ripped out.
-class MonoLogger < Logger
-
- #
- # === Synopsis
- #
- # Logger.new(name, shift_age = 7, shift_size = 1048576)
- # Logger.new(name, shift_age = 'weekly')
- #
- # === Args
- #
- # +logdev+::
- # The log device. This is a filename (String) or IO object (typically
- # +STDOUT+, +STDERR+, or an open file).
- # +shift_age+::
- # Number of old log files to keep, *or* frequency of rotation (+daily+,
- # +weekly+ or +monthly+).
- # +shift_size+::
- # Maximum logfile size (only applies when +shift_age+ is a number).
- #
- # === Description
- #
- # Create an instance.
- #
- def initialize(logdev)
- @progname = nil
- @level = DEBUG
- @default_formatter = Formatter.new
- @formatter = nil
- @logdev = nil
- if logdev
- @logdev = LocklessLogDevice.new(logdev)
- end
- end
-
- class LocklessLogDevice < LogDevice
-
- def initialize(log = nil)
- @dev = @filename = @shift_age = @shift_size = nil
- if log.respond_to?(:write) && log.respond_to?(:close)
- @dev = log
- else
- @dev = open_logfile(log)
- @filename = log
- end
- @dev.sync = true
- end
-
- def write(message)
- @dev.write(message)
- rescue Exception => ignored
- warn("log writing failed. #{ignored}")
- end
-
- def close
- @dev.close rescue nil
- end
-
- private
-
- def open_logfile(filename)
- if FileTest.exist?(filename)
- open(filename, (File::WRONLY | File::APPEND))
- else
- create_logfile(filename)
- end
- end
-
- def create_logfile(filename)
- logdev = open(filename, (File::WRONLY | File::APPEND | File::CREAT))
- add_log_header(logdev)
- logdev
- end
-
- def add_log_header(file)
- file.write(
- "# Logfile created on %s by %s\n" % [Time.now.to_s, Logger::ProgName]
- )
- end
-
- end
-
-end
+MonoLogger = Mixlib::Log::Logger
diff --git a/lib/chef/nil_argument.rb b/lib/chef/nil_argument.rb
deleted file mode 100644
index c7cbc1b39e..0000000000
--- a/lib/chef/nil_argument.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-class Chef
- NIL_ARGUMENT = Object.new
-end
diff --git a/lib/chef/node.rb b/lib/chef/node.rb
index 212b1ced14..d569eeda38 100644
--- a/lib/chef/node.rb
+++ b/lib/chef/node.rb
@@ -2,7 +2,7 @@
# Author:: Christopher Brown (<cb@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
# Author:: Tim Hinderliter (<tim@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,22 +18,24 @@
# 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/universal"
-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"
+require "forwardable" unless defined?(Forwardable)
+require "securerandom" unless defined?(SecureRandom)
+require_relative "constants"
+require_relative "config"
+require_relative "mixin/params_validate"
+require_relative "mixin/from_file"
+require_relative "mixin/deep_merge"
+require_relative "dsl/include_attribute"
+require_relative "dsl/universal"
+require_relative "environment"
+require_relative "server_api"
+require_relative "run_list"
+require_relative "node/attribute"
+require_relative "mash"
+require_relative "json_compat"
+require_relative "search/query"
+require_relative "attribute_allowlist"
+require_relative "attribute_blocklist"
class Chef
class Node
@@ -46,9 +48,9 @@ class Chef
def_delegators :attributes, :default_unless, :normal_unless, :override_unless, :set_unless
def_delegators :attributes, :read, :read!, :write, :write!, :unlink, :unlink!
- attr_accessor :recipe_list, :run_state, :override_runlist
+ attr_accessor :recipe_list, :run_state
- attr_accessor :chef_server_rest
+ attr_reader :logger
# RunContext will set itself as run_context via this setter when
# initialized. This is needed so DSL::IncludeAttribute (in particular,
@@ -64,12 +66,11 @@ class Chef
include Chef::Mixin::ParamsValidate
- NULL_ARG = Object.new
-
# Create a new Chef::Node object.
- def initialize(chef_server_rest: nil)
+ def initialize(chef_server_rest: nil, logger: nil)
@chef_server_rest = chef_server_rest
@name = nil
+ @logger = logger || Chef::Log.with_child(subsystem: "node")
@chef_environment = "_default"
@primary_runlist = Chef::RunList.new
@@ -78,7 +79,7 @@ class Chef
@policy_name = nil
@policy_group = nil
- @attributes = Chef::Node::Attribute.new({}, {}, {}, {})
+ @attributes = Chef::Node::Attribute.new({}, {}, {}, {}, self)
@run_state = {}
end
@@ -86,7 +87,6 @@ class Chef
# 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
@@ -111,13 +111,14 @@ class Chef
# Set the name of this Node, or return the current name.
def name(arg = nil)
- if arg != nil
+ if !arg.nil?
validate(
- { :name => arg },
- { :name => { :kind_of => String,
- :cannot_be => :blank,
- :regex => /^[\-[:alnum:]_:.]+$/ },
- })
+ { name: arg },
+ { name: { kind_of: String,
+ cannot_be: :blank,
+ regex: /^[\-[:alnum:]_:.]+$/ },
+ }
+ )
@name = arg
else
@name
@@ -128,7 +129,7 @@ class Chef
set_or_return(
:chef_environment,
arg,
- { :regex => /^[\-[:alnum:]_]+$/, :kind_of => String }
+ { regex: /^[\-[:alnum:]_]+$/, kind_of: String }
)
end
@@ -147,8 +148,9 @@ class Chef
#
# @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)
+ def policy_name(arg = NOT_PASSED)
+ return @policy_name if arg.equal?(NOT_PASSED)
+
validate({ policy_name: arg }, { policy_name: { kind_of: [ String, NilClass ], regex: /^[\-:.[:alnum:]_]+$/ } })
@policy_name = arg
end
@@ -169,8 +171,9 @@ class Chef
#
# @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)
+ def policy_group(arg = NOT_PASSED)
+ return @policy_group if arg.equal?(NOT_PASSED)
+
validate({ policy_group: arg }, { policy_group: { kind_of: [ String, NilClass ], regex: /^[\-:.[:alnum:]_]+$/ } })
@policy_group = arg
end
@@ -197,26 +200,18 @@ class Chef
# Set a normal attribute of this node, but auto-vivify any Mashes that
# might be missing
def normal
- attributes.top_level_breadcrumb = nil
attributes.normal
end
- def set
- Chef.log_deprecation("node.set is deprecated and will be removed in Chef 14, please use node.default/node.override (or node.normal only if you really need persistence)")
- normal
- end
-
# Set a default of this node, but auto-vivify any Mashes that might
# be missing
def default
- attributes.top_level_breadcrumb = nil
attributes.default
end
# Set an override attribute of this node, but auto-vivify any Mashes that
# might be missing
def override
- attributes.top_level_breadcrumb = nil
attributes.override
end
@@ -237,7 +232,6 @@ class Chef
end
def automatic_attrs
- attributes.top_level_breadcrumb = nil
attributes.automatic
end
@@ -295,20 +289,49 @@ class Chef
end
# Returns true if this Node expects a given role, false if not.
+ #
+ # @param role_name [String] Role to check for
+ # @return [Boolean]
def role?(role_name)
- run_list.include?("role[#{role_name}]")
+ Array(self[:roles]).include?(role_name)
end
def primary_runlist
@primary_runlist
end
+ # This boolean can be useful to determine if an override_runlist is set, it can be true
+ # even if the override_runlist is empty.
+ #
+ # (Mutators can set the override_runlist so any non-empty override_runlist is considered set)
+ #
+ # @return [Boolean] if the override run list has been set
+ def override_runlist_set?
+ !!@override_runlist_set || !override_runlist.empty?
+ end
+
+ # Accessor for override_runlist (this cannot set an empty override run list)
+ #
+ # @params args [Array] override run list to set
+ # @return [Chef::RunList] the override run list
def override_runlist(*args)
- args.length > 0 ? @override_runlist.reset!(args) : @override_runlist
+ return @override_runlist if args.length == 0
+
+ @override_runlist_set = true
+ @override_runlist.reset!(args)
+ end
+
+ # Setter for override_runlist which allows setting an empty override run list and marking it to be used
+ #
+ # @params array [Array] override run list to set
+ # @return [Chef::RunList] the override run list
+ def override_runlist=(array)
+ @override_runlist_set = true
+ @override_runlist.reset!(array)
end
def select_run_list
- @override_runlist.empty? ? @primary_runlist : @override_runlist
+ override_runlist_set? ? @override_runlist : @primary_runlist
end
# Returns an Array of roles and recipes, in the order they will be applied.
@@ -328,26 +351,48 @@ class Chef
run_list.detect { |r| r == item } ? true : false
end
- # Consume data from ohai and Attributes provided as JSON on the command line.
+ # Handles both the consumption of ohai data and possibly JSON attributes from the CLI
+ #
+ # @api private
def consume_external_attrs(ohai_data, json_cli_attrs)
- Chef::Log.debug("Extracting run list from JSON attributes provided on command line")
+ # FIXME(log): should be trace
+ logger.debug("Extracting run list from JSON attributes provided on command line")
consume_attributes(json_cli_attrs)
self.automatic_attrs = ohai_data
+ fix_automatic_attributes
+ end
+ # This is for ohai plugins to consume ohai data and have it merged, it should probably be renamed
+ #
+ # @api private
+ def consume_ohai_data(ohai_data)
+ self.automatic_attrs = Chef::Mixin::DeepMerge.merge(automatic_attrs, ohai_data)
+ fix_automatic_attributes
+ end
+
+ # Always ensure that certain automatic attributes are populated and constructed correctly
+ #
+ # @api private
+ def fix_automatic_attributes
platform, version = Chef::Platform.find_platform_and_version(self)
- Chef::Log.debug("Platform is #{platform} version #{version}")
- self.automatic[:platform] = platform
- self.automatic[:platform_version] = version
+ # FIXME(log): should be trace
+ logger.debug("Platform is #{platform} version #{version}")
+ automatic[:platform] = platform
+ automatic[:platform_version] = Chef::VersionString.new(version)
+ automatic[:chef_guid] = Chef::Config[:chef_guid] || ( Chef::Config[:chef_guid] = node_uuid )
+ automatic[:name] = name
+ automatic[:chef_environment] = chef_environment
end
# 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")
+ # FIXME(log): should be trace
+ logger.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
+ tags # make sure they're defined
end
# Lazy initializer for tags attribute
@@ -371,7 +416,8 @@ 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} from CLI options")
+
+ logger.info("Setting the run_list to #{new_run_list} from CLI options")
run_list(new_run_list)
end
attrs
@@ -398,8 +444,8 @@ class Chef
# Clear defaults and overrides, so that any deleted attributes
# between runs are still gone.
def reset_defaults_and_overrides
- self.default.clear
- self.override.clear
+ default.clear
+ override.clear
end
# Expands the node's run list and sets the default and override
@@ -418,7 +464,7 @@ class Chef
expansion = run_list.expand(chef_environment, data_source)
raise Chef::Exceptions::MissingRole, expansion if expansion.errors?
- self.tags # make sure they're defined
+ tags # make sure they're defined
automatic_attrs[:recipes] = expansion.recipes.with_duplicate_names
automatic_attrs[:expanded_run_list] = expansion.recipes.with_fully_qualified_names_and_version_constraints
@@ -426,6 +472,7 @@ class Chef
apply_expansion_attributes(expansion)
+ automatic_attrs[:chef_environment] = chef_environment
expansion
end
@@ -447,13 +494,10 @@ class Chef
# Transform the node to a Hash
def to_hash
- index_hash = Hash.new
+ index_hash = attributes.to_hash
index_hash["chef_type"] = "node"
index_hash["name"] = name
index_hash["chef_environment"] = chef_environment
- attribute.each do |key, value|
- index_hash[key] = value
- end
index_hash["recipe"] = run_list.recipe_names if run_list.recipe_names.length > 0
index_hash["role"] = run_list.role_names if run_list.role_names.length > 0
index_hash["run_list"] = run_list.run_list_items
@@ -464,10 +508,10 @@ class Chef
display = {}
display["name"] = name
display["chef_environment"] = chef_environment
- display["automatic"] = automatic_attrs
- display["normal"] = normal_attrs
- display["default"] = attributes.combined_default
- display["override"] = attributes.combined_override
+ display["automatic"] = attributes.automatic.to_hash
+ display["normal"] = attributes.normal.to_hash
+ display["default"] = attributes.combined_default.to_hash
+ display["override"] = attributes.combined_override.to_hash
display["run_list"] = run_list.run_list_items
display
end
@@ -482,13 +526,13 @@ class Chef
"name" => name,
"chef_environment" => chef_environment,
"json_class" => self.class.name,
- "automatic" => attributes.automatic,
- "normal" => attributes.normal,
+ "automatic" => attributes.automatic.to_hash,
+ "normal" => attributes.normal.to_hash,
"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 },
+ "default" => attributes.combined_default.to_hash,
+ "override" => attributes.combined_override.to_hash,
+ # Render correctly for run_list items so malformed json does not result
+ "run_list" => @primary_runlist.run_list.map(&: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
@@ -511,40 +555,41 @@ class Chef
self
end
- # Create a Chef::Node from JSON
- def self.json_create(o)
- Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::Node#from_hash")
- from_hash(o)
- end
-
def self.from_hash(o)
- return o if o.kind_of? Chef::Node
+ return o if o.is_a? Chef::Node
+
node = new
node.name(o["name"])
- node.chef_environment(o["chef_environment"])
- if o.has_key?("attributes")
+
+ node.policy_name = o["policy_name"] if o.key?("policy_name")
+ node.policy_group = o["policy_group"] if o.key?("policy_group")
+
+ unless node.policy_group.nil?
+ node.chef_environment(o["policy_group"])
+ else
+ node.chef_environment(o["chef_environment"])
+ end
+
+ if o.key?("attributes")
node.normal_attrs = o["attributes"]
end
- node.automatic_attrs = Mash.new(o["automatic"]) if o.has_key?("automatic")
- node.normal_attrs = Mash.new(o["normal"]) if o.has_key?("normal")
- node.default_attrs = Mash.new(o["default"]) if o.has_key?("default")
- node.override_attrs = Mash.new(o["override"]) if o.has_key?("override")
+ node.automatic_attrs = Mash.new(o["automatic"]) if o.key?("automatic")
+ node.normal_attrs = Mash.new(o["normal"]) if o.key?("normal")
+ node.default_attrs = Mash.new(o["default"]) if o.key?("default")
+ node.override_attrs = Mash.new(o["override"]) if o.key?("override")
- if o.has_key?("run_list")
+ if o.key?("run_list")
node.run_list.reset!(o["run_list"])
- elsif o.has_key?("recipes")
+ elsif o.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
def self.list_by_environment(environment, inflate = false)
if inflate
- response = Hash.new
+ response = {}
Chef::Search::Query.new.search(:node, "chef_environment:#{environment}") { |n| response[n.name] = n unless n.nil? }
response
else
@@ -554,7 +599,7 @@ class Chef
def self.list(inflate = false)
if inflate
- response = Hash.new
+ response = {}
Chef::Search::Query.new.search(:node) do |n|
n = Chef::Node.from_hash(n)
response[n.name] = n unless n.nil?
@@ -567,8 +612,9 @@ class Chef
def self.find_or_create(node_name)
load(node_name)
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => e
raise unless e.response.code == "404"
+
node = build(node_name)
node.create
end
@@ -596,18 +642,13 @@ class Chef
# so then POST to create.
begin
if Chef::Config[:why_run]
- Chef::Log.warn("In why-run mode, so NOT performing node save.")
+ logger.warn("In why-run mode, so NOT performing node save.")
else
chef_server_rest.put("nodes/#{name}", data_for_save)
end
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => e
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
@@ -619,7 +660,7 @@ class Chef
def create
chef_server_rest.post("nodes", data_for_save)
self
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => 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
@@ -635,15 +676,15 @@ class Chef
end
def ==(other)
- if other.kind_of?(self.class)
- self.name == other.name
+ if other.is_a?(self.class)
+ name == other.name
else
false
end
end
def <=>(other)
- self.name <=> other.name
+ name <=> other.name
end
private
@@ -652,8 +693,9 @@ class Chef
trimmed_data = data_for_save_without_policyfile_attrs
chef_server_rest.put("nodes/#{name}", trimmed_data)
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => e
raise e unless e.response.code == "404"
+
chef_server_rest.post("nodes", trimmed_data)
end
@@ -664,18 +706,68 @@ class Chef
end
end
+ # a method to handle the renamed configuration from whitelist -> allowed
+ # and to throw a deprecation warning when the old configuration is set
+ #
+ # @param [String] level the attribute level
+ def allowlist_or_whitelist_config(level)
+ if Chef::Config["#{level}_attribute_whitelist".to_sym]
+ Chef.deprecated(:attribute_blacklist_configuration, "Attribute whitelist configurations have been deprecated. Use the allowed_LEVEL_attribute configs instead")
+ Chef::Config["#{level}_attribute_whitelist".to_sym]
+ else
+ Chef::Config["allowed_#{level}_attributes".to_sym]
+ end
+ end
+
+ # a method to handle the renamed configuration from blacklist -> blocked
+ # and to throw a deprecation warning when the old configuration is set
+ #
+ # @param [String] level the attribute level
+ def blocklist_or_blacklist_config(level)
+ if Chef::Config["#{level}_attribute_blacklist".to_sym]
+ Chef.deprecated(:attribute_blacklist_configuration, "Attribute blacklist configurations have been deprecated. Use the blocked_LEVEL_attribute configs instead")
+ Chef::Config["#{level}_attribute_blacklist".to_sym]
+ else
+ Chef::Config["blocked_#{level}_attributes".to_sym]
+ end
+ end
+
def data_for_save
data = for_json
%w{automatic default normal override}.each do |level|
- whitelist_config_option = "#{level}_attribute_whitelist".to_sym
- whitelist = Chef::Config[whitelist_config_option]
- unless whitelist.nil? # nil => save everything
- Chef::Log.info("Whitelisting #{level} node attributes for save.")
- data[level] = Chef::Whitelist.filter(data[level], whitelist)
+ allowlist = allowlist_or_whitelist_config(level)
+ unless allowlist.nil? # nil => save everything
+ logger.info("Allowing #{level} node attributes for save.")
+ data[level] = Chef::AttributeAllowlist.filter(data[level], allowlist)
+ end
+
+ blocklist = blocklist_or_blacklist_config(level)
+ unless blocklist.nil? # nil => remove nothing
+ logger.info("Blocking #{level} node attributes for save")
+ data[level] = Chef::AttributeBlocklist.filter(data[level], blocklist)
end
end
data
end
+ # Returns a UUID that uniquely identifies this node for reporting reasons.
+ #
+ # The node is read in from disk if it exists, or it's generated if it does
+ # does not exist.
+ #
+ # @return [String] UUID for the node
+ #
+ def node_uuid
+ path = File.expand_path(Chef::Config[:chef_guid_path])
+ dir = File.dirname(path)
+
+ unless File.exists?(path)
+ FileUtils.mkdir_p(dir)
+ File.write(path, SecureRandom.uuid)
+ end
+
+ File.open(path).first.chomp
+ end
+
end
end
diff --git a/lib/chef/node/attribute.rb b/lib/chef/node/attribute.rb
index 95b3b09f7e..29b60a98d5 100644
--- a/lib/chef/node/attribute.rb
+++ b/lib/chef/node/attribute.rb
@@ -1,7 +1,7 @@
#--
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: AJ Christensen (<aj@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,11 +17,14 @@
# limitations under the License.
#
-require "chef/node/immutable_collections"
-require "chef/node/attribute_collections"
-require "chef/decorator/unchain"
-require "chef/mixin/deep_merge"
-require "chef/log"
+require_relative "mixin/deep_merge_cache"
+require_relative "mixin/immutablize_hash"
+require_relative "mixin/state_tracking"
+require_relative "immutable_collections"
+require_relative "attribute_collections"
+require_relative "../decorator/unchain"
+require_relative "../mixin/deep_merge"
+require_relative "../log"
class Chef
class Node
@@ -34,284 +37,264 @@ class Chef
class Attribute < Mash
include Immutablize
-
+ # FIXME: what is include Enumerable doing up here, when down below we delegate
+ # most of the Enumerable/Hash things to the underlying merged ImmutableHash. That
+ # is, in fact, the correct, thing to do, while including Enumerable to try to create
+ # a hash-like API gets lots of things wrong because of the difference between the
+ # Hash `each do |key, value|` vs the Array-like `each do |value|` API that Enumerable
+ # expects. This include should probably be deleted?
include Enumerable
+ include Chef::Node::Mixin::DeepMergeCache
+ include Chef::Node::Mixin::StateTracking
+ include Chef::Node::Mixin::ImmutablizeHash
+
# List of the component attribute hashes, in order of precedence, low to
# high.
- COMPONENTS = [
- :@default,
- :@env_default,
- :@role_default,
- :@force_default,
- :@normal,
- :@override,
- :@role_override,
- :@env_override,
- :@force_override,
- :@automatic,
- ].freeze
-
- DEFAULT_COMPONENTS = [
- :@default,
- :@env_default,
- :@role_default,
- :@force_default,
- ]
-
- OVERRIDE_COMPONENTS = [
- :@override,
- :@role_override,
- :@env_override,
- :@force_override,
- ]
-
- [:all?,
- :any?,
- :assoc,
- :chunk,
- :collect,
- :collect_concat,
- :compare_by_identity,
- :compare_by_identity?,
- :count,
- :cycle,
- :detect,
- :drop,
- :drop_while,
- :each,
- :each_cons,
- :each_entry,
- :each_key,
- :each_pair,
- :each_slice,
- :each_value,
- :each_with_index,
- :each_with_object,
- :empty?,
- :entries,
- :except,
- :fetch,
- :find,
- :find_all,
- :find_index,
- :first,
- :flat_map,
- :flatten,
- :grep,
- :group_by,
- :has_value?,
- :include?,
- :index,
- :inject,
- :invert,
- :key,
- :keys,
- :length,
- :map,
- :max,
- :max_by,
- :merge,
- :min,
- :min_by,
- :minmax,
- :minmax_by,
- :none?,
- :one?,
- :partition,
- :rassoc,
- :reduce,
- :reject,
- :reverse_each,
- :select,
- :size,
- :slice_before,
- :sort,
- :sort_by,
- :store,
- :symbolize_keys,
- :take,
- :take_while,
- :to_a,
- :to_h,
- :to_hash,
- :to_set,
- :value?,
- :values,
- :values_at,
- :zip].each do |delegated_method|
- define_method(delegated_method) do |*args, &block|
- merged_attributes.send(delegated_method, *args, &block)
- end
- end
-
- # return the cookbook level default attribute component
+ COMPONENTS = %i{
+ @default
+ @env_default
+ @role_default
+ @force_default
+ @normal
+ @override
+ @role_override
+ @env_override
+ @force_override
+ @automatic
+ }.freeze
+
+ DEFAULT_COMPONENTS = %i{
+ @default
+ @env_default
+ @role_default
+ @force_default
+ }.freeze
+
+ OVERRIDE_COMPONENTS = %i{
+ @override
+ @role_override
+ @env_override
+ @force_override
+ }.freeze
+
+ ENUM_METHODS = %i{
+ all?
+ any?
+ assoc
+ chunk
+ collect
+ collect_concat
+ compare_by_identity
+ compare_by_identity?
+ count
+ cycle
+ detect
+ drop
+ drop_while
+ each
+ each_cons
+ each_entry
+ each_key
+ each_pair
+ each_slice
+ each_value
+ each_with_index
+ each_with_object
+ empty?
+ entries
+ except
+ fetch
+ find
+ find_all
+ find_index
+ first
+ flat_map
+ flatten
+ grep
+ group_by
+ has_value?
+ include?
+ index
+ inject
+ invert
+ key
+ keys
+ length
+ map
+ max
+ max_by
+ merge
+ min
+ min_by
+ minmax
+ minmax_by
+ none?
+ one?
+ partition
+ rassoc
+ reduce
+ reject
+ reverse_each
+ select
+ size
+ slice_before
+ sort
+ sort_by
+ store
+ symbolize_keys
+ take
+ take_while
+ to_a
+ to_h
+ to_hash
+ to_json
+ to_set
+ to_yaml
+ value?
+ values
+ values_at
+ zip
+ }.freeze
+
+ ENUM_METHODS.each do |delegated_method|
+ define_method(delegated_method) do |*args, &block|
+ merged_attributes.send(delegated_method, *args, &block)
+ end
+ end
+
+ # return the cookbook level default attribute component
attr_reader :default
- # return the role level default attribute component
+ # return the role level default attribute component
attr_reader :role_default
- # return the environment level default attribute component
+ # return the environment level default attribute component
attr_reader :env_default
- # return the force_default level attribute component
+ # return the force_default level attribute component
attr_reader :force_default
- # return the "normal" level attribute component
+ # return the "normal" level attribute component
attr_reader :normal
- # return the cookbook level override attribute component
+ # return the cookbook level override attribute component
attr_reader :override
- # return the role level override attribute component
+ # return the role level override attribute component
attr_reader :role_override
- # return the enviroment level override attribute component
+ # return the environment level override attribute component
attr_reader :env_override
- # return the force override level attribute component
+ # return the force override level attribute component
attr_reader :force_override
- # return the automatic level attribute component
+ # return the automatic level attribute component
attr_reader :automatic
- # This is used to track the top level key as we descend through method chaining into
- # a precedence level (e.g. node.default['foo']['bar']['baz']= results in 'foo' here). We
- # need this so that when we hit the end of a method chain which results in a mutator method
- # that we can invalidate the whole top-level deep merge cache for the top-level key. It is
- # the responsibility of the accessor on the Chef::Node object to reset this to nil, and then
- # the first VividMash#[] call can ||= and set this to the first key we encounter.
- attr_accessor :top_level_breadcrumb
-
- # Cache of deep merged values by top-level key. This is a simple hash which has keys that are the
- # top-level keys of the node object, and we save the computed deep-merge for that key here. There is
- # no cache of subtrees.
- attr_accessor :deep_merge_cache
-
- def initialize(normal, default, override, automatic)
- @default = VividMash.new(self, default)
- @env_default = VividMash.new(self, {})
- @role_default = VividMash.new(self, {})
- @force_default = VividMash.new(self, {})
-
- @normal = VividMash.new(self, normal)
-
- @override = VividMash.new(self, override)
- @role_override = VividMash.new(self, {})
- @env_override = VividMash.new(self, {})
- @force_override = VividMash.new(self, {})
-
- @automatic = VividMash.new(self, automatic)
-
- @merged_attributes = nil
- @combined_override = nil
- @combined_default = nil
- @top_level_breadcrumb = nil
- @deep_merge_cache = {}
- end
-
- # Debug what's going on with an attribute. +args+ is a path spec to the
- # attribute you're interested in. For example, to debug where the value
- # of `node[:network][:default_interface]` is coming from, use:
- # debug_value(:network, :default_interface).
- # The return value is an Array of Arrays. The Arrays
- # are pairs of `["precedence_level", value]`, where precedence level is
- # the component, such as role default, normal, etc. and value is the
- # attribute value set at that precedence level. If there is no value at
- # that precedence level, +value+ will be the symbol +:not_present+.
+ def initialize(normal, default, override, automatic, node = nil)
+ @default = VividMash.new(default, self, node, :default)
+ @env_default = VividMash.new({}, self, node, :env_default)
+ @role_default = VividMash.new({}, self, node, :role_default)
+ @force_default = VividMash.new({}, self, node, :force_default)
+
+ @normal = VividMash.new(normal, self, node, :normal)
+
+ @override = VividMash.new(override, self, node, :override)
+ @role_override = VividMash.new({}, self, node, :role_override)
+ @env_override = VividMash.new({}, self, node, :env_override)
+ @force_override = VividMash.new({}, self, node, :force_override)
+
+ @automatic = VividMash.new(automatic, self, node, :automatic)
+
+ super(nil, self, node, :merged)
+ end
+
+ # Debug what's going on with an attribute. +args+ is a path spec to the
+ # attribute you're interested in. For example, to debug where the value
+ # of `node[:network][:default_interface]` is coming from, use:
+ # debug_value(:network, :default_interface).
+ # The return value is an Array of Arrays. The Arrays
+ # are pairs of `["precedence_level", value]`, where precedence level is
+ # the component, such as role default, normal, etc. and value is the
+ # attribute value set at that precedence level. If there is no value at
+ # that precedence level, +value+ will be the symbol +:not_present+.
def debug_value(*args)
COMPONENTS.map do |component|
- ivar = instance_variable_get(component)
- value = args.inject(ivar) do |so_far, key|
- if so_far == :not_present
- :not_present
- elsif so_far.has_key?(key)
- so_far[key]
- else
+ value =
+ begin
+ instance_variable_get(component).read!(*args)
+ rescue
:not_present
end
- end
[component.to_s.sub(/^@/, ""), value]
end
end
- # Invalidate a key in the deep_merge_cache. If called with nil, or no arg, this will invalidate
- # the entire deep_merge cache. In the case of the user doing node.default['foo']['bar']['baz']=
- # that eventually results in a call to reset_cache('foo') here. A node.default=hash_thing call
- # must invalidate the entire cache and re-deep-merge the entire node object.
- def reset_cache(path = nil)
- if path.nil?
- @deep_merge_cache = {}
- else
- deep_merge_cache.delete(path.to_s)
- end
- end
-
- alias :reset :reset_cache
-
- # Set the cookbook level default attribute component to +new_data+.
+ # Set the cookbook level default attribute component to +new_data+.
def default=(new_data)
reset
- @default = VividMash.new(self, new_data)
+ @default = VividMash.new(new_data, self, __node__, :default)
end
- # Set the role level default attribute component to +new_data+
+ # Set the role level default attribute component to +new_data+
def role_default=(new_data)
reset
- @role_default = VividMash.new(self, new_data)
+ @role_default = VividMash.new(new_data, self, __node__, :role_default)
end
- # Set the environment level default attribute component to +new_data+
+ # Set the environment level default attribute component to +new_data+
def env_default=(new_data)
reset
- @env_default = VividMash.new(self, new_data)
+ @env_default = VividMash.new(new_data, self, __node__, :env_default)
end
- # Set the force_default (+default!+) level attributes to +new_data+
+ # Set the force_default (+default!+) level attributes to +new_data+
def force_default=(new_data)
reset
- @force_default = VividMash.new(self, new_data)
+ @force_default = VividMash.new(new_data, self, __node__, :force_default)
end
- # Set the normal level attribute component to +new_data+
+ # Set the normal level attribute component to +new_data+
def normal=(new_data)
reset
- @normal = VividMash.new(self, new_data)
+ @normal = VividMash.new(new_data, self, __node__, :normal)
end
- # Set the cookbook level override attribute component to +new_data+
+ # Set the cookbook level override attribute component to +new_data+
def override=(new_data)
reset
- @override = VividMash.new(self, new_data)
+ @override = VividMash.new(new_data, self, __node__, :override)
end
- # Set the role level override attribute component to +new_data+
+ # Set the role level override attribute component to +new_data+
def role_override=(new_data)
reset
- @role_override = VividMash.new(self, new_data)
+ @role_override = VividMash.new(new_data, self, __node__, :role_override)
end
- # Set the environment level override attribute component to +new_data+
+ # Set the environment level override attribute component to +new_data+
def env_override=(new_data)
reset
- @env_override = VividMash.new(self, new_data)
+ @env_override = VividMash.new(new_data, self, __node__, :env_override)
end
def force_override=(new_data)
reset
- @force_override = VividMash.new(self, new_data)
+ @force_override = VividMash.new(new_data, self, __node__, :force_override)
end
def automatic=(new_data)
reset
- @automatic = VividMash.new(self, new_data)
+ @automatic = VividMash.new(new_data, self, __node__, :automatic)
end
- #
- # Deleting attributes
- #
+ #
+ # Deleting attributes
+ #
- # clears attributes from all precedence levels
+ # clears attributes from all precedence levels
def rm(*args)
with_deep_merged_return_value(self, *args) do
rm_default(*args)
@@ -320,11 +303,11 @@ class Chef
end
end
- # clears attributes from all default precedence levels
- #
- # similar to: force_default!['foo']['bar'].delete('baz')
- # - does not autovivify
- # - does not trainwreck if interior keys do not exist
+ # clears attributes from all default precedence levels
+ #
+ # similar to: force_default!['foo']['bar'].delete('baz')
+ # - does not autovivify
+ # - does not trainwreck if interior keys do not exist
def rm_default(*args)
with_deep_merged_return_value(combined_default, *args) do
default.unlink(*args)
@@ -334,20 +317,20 @@ class Chef
end
end
- # clears attributes from normal precedence
- #
- # equivalent to: normal!['foo']['bar'].delete('baz')
- # - does not autovivify
- # - does not trainwreck if interior keys do not exist
+ # clears attributes from normal precedence
+ #
+ # equivalent to: normal!['foo']['bar'].delete('baz')
+ # - does not autovivify
+ # - does not trainwreck if interior keys do not exist
def rm_normal(*args)
normal.unlink(*args)
end
- # clears attributes from all override precedence levels
- #
- # equivalent to: force_override!['foo']['bar'].delete('baz')
- # - does not autovivify
- # - does not trainwreck if interior keys do not exist
+ # clears attributes from all override precedence levels
+ #
+ # equivalent to: force_override!['foo']['bar'].delete('baz')
+ # - does not autovivify
+ # - does not trainwreck if interior keys do not exist
def rm_override(*args)
with_deep_merged_return_value(combined_override, *args) do
override.unlink(*args)
@@ -360,6 +343,7 @@ class Chef
def with_deep_merged_return_value(obj, *path, last)
hash = obj.read(*path)
return nil unless hash.is_a?(Hash)
+
ret = hash[last]
yield
ret
@@ -367,143 +351,132 @@ class Chef
private :with_deep_merged_return_value
- #
- # Replacing attributes without merging
- #
+ #
+ # Replacing attributes without merging
+ #
- # sets default attributes without merging
- #
- # - this API autovivifies (and cannot trainwreck)
+ # sets default attributes without merging
+ #
+ # - this API autovivifies (and cannot trainwreck)
def default!(*args)
return Decorator::Unchain.new(self, :default!) unless args.length > 0
+
write(:default, *args)
end
- # sets normal attributes without merging
- #
- # - this API autovivifies (and cannot trainwreck)
+ # sets normal attributes without merging
+ #
+ # - this API autovivifies (and cannot trainwreck)
def normal!(*args)
return Decorator::Unchain.new(self, :normal!) unless args.length > 0
+
write(:normal, *args)
end
- # sets override attributes without merging
- #
- # - this API autovivifies (and cannot trainwreck)
+ # sets override attributes without merging
+ #
+ # - this API autovivifies (and cannot trainwreck)
def override!(*args)
return Decorator::Unchain.new(self, :override!) unless args.length > 0
+
write(:override, *args)
end
- # clears from all default precedence levels and then sets force_default
- #
- # - this API autovivifies (and cannot trainwreck)
+ # clears from all default precedence levels and then sets force_default
+ #
+ # - this API autovivifies (and cannot trainwreck)
def force_default!(*args)
return Decorator::Unchain.new(self, :force_default!) unless args.length > 0
+
value = args.pop
rm_default(*args)
write(:force_default, *args, value)
end
- # clears from all override precedence levels and then sets force_override
+ # clears from all override precedence levels and then sets force_override
def force_override!(*args)
return Decorator::Unchain.new(self, :force_override!) unless args.length > 0
+
value = args.pop
rm_override(*args)
write(:force_override, *args, value)
end
- # method-style access to attributes
-
- def read(*path)
- merged_attributes.read(*path)
+ #
+ # Accessing merged attributes.
+ #
+ # Note that merged_attributes('foo', 'bar', 'baz') can be called to compute only the
+ # deep merge of node['foo']['bar']['baz'], but in practice we currently always compute
+ # all of node['foo'] even if the user only requires node['foo']['bar']['baz'].
+ #
+ def merged_attributes(*path)
+ merge_all(path)
end
- def read!(*path)
- merged_attributes.read!(*path)
+ def combined_override(*path)
+ ret = merge_overrides(path)
+ ret == NIL ? nil : ret
end
- def exist?(*path)
- merged_attributes.exist?(*path)
+ def combined_default(*path)
+ ret = merge_defaults(path)
+ ret == NIL ? nil : ret
end
- def write(level, *args, &block)
- self.send(level).write(*args, &block)
- end
+ def normal_unless(*args)
+ return Decorator::Unchain.new(self, :normal_unless) unless args.length > 0
- def write!(level, *args, &block)
- self.send(level).write!(*args, &block)
+ write(:normal, *args) if normal.read(*args[0...-1]).nil?
end
- def unlink(level, *path)
- self.send(level).unlink(*path)
- end
+ def default_unless(*args)
+ return Decorator::Unchain.new(self, :default_unless) unless args.length > 0
- def unlink!(level, *path)
- self.send(level).unlink!(*path)
+ write(:default, *args) if default.read(*args[0...-1]).nil?
end
- #
- # Accessing merged attributes.
- #
- # Note that merged_attributes('foo', 'bar', 'baz') can be called to compute only the
- # deep merge of node['foo']['bar']['baz'], but in practice we currently always compute
- # all of node['foo'] even if the user only requires node['foo']['bar']['baz'].
- #
+ def override_unless(*args)
+ return Decorator::Unchain.new(self, :override_unless) unless args.length > 0
- def merged_attributes(*path)
- # immutablize(
- merge_all(path)
- # )
+ write(:override, *args) if override.read(*args[0...-1]).nil?
end
- def combined_override(*path)
- immutablize(merge_overrides(path))
+ def has_key?(key)
+ COMPONENTS.any? do |component_ivar|
+ instance_variable_get(component_ivar).key?(key)
+ end
end
- def combined_default(*path)
- immutablize(merge_defaults(path))
- end
+ # method-style access to attributes (has to come after the prepended ImmutablizeHash)
- def normal_unless(*args)
- return Decorator::Unchain.new(self, :normal_unless) unless args.length > 0
- write(:normal, *args) if read(*args[0...-1]).nil?
+ def read(*path)
+ merged_attributes.read(*path)
end
- def default_unless(*args)
- return Decorator::Unchain.new(self, :default_unless) unless args.length > 0
- write(:default, *args) if read(*args[0...-1]).nil?
+ alias :dig :read
+
+ def read!(*path)
+ merged_attributes.read!(*path)
end
- def override_unless(*args)
- return Decorator::Unchain.new(self, :override_unless) unless args.length > 0
- write(:override, *args) if read(*args[0...-1]).nil?
+ def exist?(*path)
+ merged_attributes.exist?(*path)
end
- def set_unless(*args)
- Chef.log_deprecation("node.set_unless is deprecated and will be removed in Chef 14, please use node.default_unless/node.override_unless (or node.normal_unless if you really need persistence)")
- return Decorator::Unchain.new(self, :default_unless) unless args.length > 0
- write(:normal, *args) if read(*args[0...-1]).nil?
+ def write(level, *args, &block)
+ send(level).write(*args, &block)
end
- def [](key)
- if deep_merge_cache.has_key?(key.to_s)
- # return the cache of the deep merged values by top-level key
- deep_merge_cache[key.to_s]
- else
- # save all the work of computing node[key]
- deep_merge_cache[key.to_s] = merged_attributes(key)
- end
+ def write!(level, *args, &block)
+ send(level).write!(*args, &block)
end
- def []=(key, value)
- raise Exceptions::ImmutableAttributeModification
+ def unlink(level, *path)
+ send(level).unlink(*path)
end
- def has_key?(key)
- COMPONENTS.any? do |component_ivar|
- instance_variable_get(component_ivar).has_key?(key)
- end
+ def unlink!(level, *path)
+ send(level).unlink!(*path)
end
alias :attribute? :has_key?
@@ -513,47 +486,28 @@ class Chef
alias :each_attribute :each
- def method_missing(symbol, *args)
- if symbol == :to_ary
- merged_attributes.send(symbol, *args)
- elsif args.empty?
- Chef.log_deprecation %q{method access to node attributes (node.foo.bar) is deprecated and will be removed in Chef 13, please use bracket syntax (node["foo"]["bar"])}
- if key?(symbol)
- self[symbol]
- else
- raise NoMethodError, "Undefined method or attribute `#{symbol}' on `node'"
- end
- elsif symbol.to_s =~ /=$/
- Chef.log_deprecation %q{method setting of node attributes (node.foo="bar") is deprecated and will be removed in Chef 13, please use bracket syntax (node["foo"]="bar")}
- key_to_set = symbol.to_s[/^(.+)=$/, 1]
- self[key_to_set] = (args.length == 1 ? args[0] : args)
- else
- raise NoMethodError, "Undefined node attribute or method `#{symbol}' on `node'"
- end
- end
-
def to_s
merged_attributes.to_s
end
def inspect
- "#<#{self.class} " << (COMPONENTS + [:@merged_attributes, :@properties]).map do |iv|
+ "#<#{self.class} " << (COMPONENTS + %i{@merged_attributes @properties}).map do |iv|
"#{iv}=#{instance_variable_get(iv).inspect}"
end.join(", ") << ">"
end
private
- # Helper method for merge_all/merge_defaults/merge_overrides.
- #
- # apply_path(thing, [ "foo", "bar", "baz" ]) = thing["foo"]["bar"]["baz"]
- #
- # The path value can be nil in which case the whole component is returned.
- #
- # It returns nil (does not raise an exception) if it walks off the end of an Mash/Hash/Array, it does not
- # raise any TypeError if it attempts to apply a hash key to an Integer/String/TrueClass, and just returns
- # nil in that case.
- #
+ # Helper method for merge_all/merge_defaults/merge_overrides.
+ #
+ # apply_path(thing, [ "foo", "bar", "baz" ]) = thing["foo"]["bar"]["baz"]
+ #
+ # The path value can be nil in which case the whole component is returned.
+ #
+ # It returns nil (does not raise an exception) if it walks off the end of an Mash/Hash/Array, it does not
+ # raise any TypeError if it attempts to apply a hash key to an Integer/String/TrueClass, and just returns
+ # nil in that case.
+ #
def apply_path(component, path)
path ||= []
path.inject(component) do |val, path_arg|
@@ -562,32 +516,32 @@ class Chef
if !val.respond_to?(:has_key?)
# Have an Array-like thing
val[path_arg]
- elsif val.has_key?(path_arg)
+ elsif val.key?(path_arg)
# Hash-like thing (must check has_key? first to protect against Autovivification)
val[path_arg]
else
- nil
+ NIL
end
else
- nil
+ NIL
end
end
end
- # For elements like Fixnums, true, nil...
+ # For elements like Fixnums, true, nil...
def safe_dup(e)
e.dup
rescue TypeError
e
end
- # Deep merge all attribute levels using hash-only merging between different precidence
- # levels (so override arrays completely replace arrays set at any default level).
- #
- # The path allows for selectively deep-merging a subtree of the node object.
- #
- # @param path [Array] Array of args to method chain to descend into the node object
- # @return [attr] Deep Merged values (may be VividMash, Hash, Array, etc) from the node object
+ # Deep merge all attribute levels using hash-only merging between different precedence
+ # levels (so override arrays completely replace arrays set at any default level).
+ #
+ # The path allows for selectively deep-merging a subtree of the node object.
+ #
+ # @param path [Array] Array of args to method chain to descend into the node object
+ # @return [attr] Deep Merged values (may be VividMash, Hash, Array, etc) from the node object
def merge_all(path)
components = [
merge_defaults(path),
@@ -596,44 +550,113 @@ class Chef
apply_path(@automatic, path),
]
- components.map! do |component|
- safe_dup(component)
- end
-
- return nil if components.compact.empty?
-
- components.inject(ImmutableMash.new({})) do |merged, component|
- Chef::Mixin::DeepMerge.hash_only_merge!(merged, component)
+ ret = components.inject(NIL) do |merged, component|
+ hash_only_merge!(merged, component)
end
+ ret == NIL ? nil : ret
end
- # Deep merge the default attribute levels with array merging.
- #
- # The path allows for selectively deep-merging a subtree of the node object.
- #
- # @param path [Array] Array of args to method chain to descend into the node object
- # @return [attr] Deep Merged values (may be VividMash, Hash, Array, etc) from the node object
+ # Deep merge the default attribute levels with array merging.
+ #
+ # The path allows for selectively deep-merging a subtree of the node object.
+ #
+ # @param path [Array] Array of args to method chain to descend into the node object
+ # @return [attr] Deep Merged values (may be VividMash, Hash, Array, etc) from the node object
def merge_defaults(path)
- DEFAULT_COMPONENTS.inject(nil) do |merged, component_ivar|
+ DEFAULT_COMPONENTS.inject(NIL) do |merged, component_ivar|
component_value = apply_path(instance_variable_get(component_ivar), path)
- Chef::Mixin::DeepMerge.deep_merge(component_value, merged)
+ deep_merge!(merged, component_value)
end
end
- # Deep merge the override attribute levels with array merging.
- #
- # The path allows for selectively deep-merging a subtree of the node object.
- #
- # @param path [Array] Array of args to method chain to descend into the node object
- # @return [attr] Deep Merged values (may be VividMash, Hash, Array, etc) from the node object
+ # Deep merge the override attribute levels with array merging.
+ #
+ # The path allows for selectively deep-merging a subtree of the node object.
+ #
+ # @param path [Array] Array of args to method chain to descend into the node object
+ # @return [attr] Deep Merged values (may be VividMash, Hash, Array, etc) from the node object
def merge_overrides(path)
- OVERRIDE_COMPONENTS.inject(nil) do |merged, component_ivar|
+ OVERRIDE_COMPONENTS.inject(NIL) do |merged, component_ivar|
component_value = apply_path(instance_variable_get(component_ivar), path)
- Chef::Mixin::DeepMerge.deep_merge(component_value, merged)
+ deep_merge!(merged, component_value)
end
end
- end
+ # needed for __path__
+ def convert_key(key)
+ key.is_a?(Symbol) ? key.to_s : key
+ end
+
+ NIL = Object.new
+ # @api private
+ def deep_merge!(merge_onto, merge_with)
+ # If there are two Hashes, recursively merge.
+ if merge_onto.is_a?(Hash) && merge_with.is_a?(Hash)
+ merge_with.each do |key, merge_with_value|
+ value =
+ if merge_onto.key?(key)
+ deep_merge!(safe_dup(merge_onto[key]), merge_with_value)
+ else
+ merge_with_value
+ end
+
+ # internal_set bypasses converting keys, does convert values and allows writing to immutable mashes
+ merge_onto.internal_set(key, value)
+ end
+ merge_onto
+
+ elsif merge_onto.is_a?(Array) && merge_with.is_a?(Array)
+ merge_onto |= merge_with
+
+ # If merge_with is NIL, don't replace merge_onto
+ elsif merge_with == NIL
+ merge_onto
+
+ # In all other cases, replace merge_onto with merge_with
+ else
+ if merge_with.is_a?(Hash)
+ Chef::Node::ImmutableMash.new(merge_with)
+ elsif merge_with.is_a?(Array)
+ Chef::Node::ImmutableArray.new(merge_with)
+ else
+ merge_with
+ end
+ end
+ end
+
+ # @api private
+ def hash_only_merge!(merge_onto, merge_with)
+ # If there are two Hashes, recursively merge.
+ if merge_onto.is_a?(Hash) && merge_with.is_a?(Hash)
+ merge_with.each do |key, merge_with_value|
+ value =
+ if merge_onto.key?(key)
+ hash_only_merge!(safe_dup(merge_onto[key]), merge_with_value)
+ else
+ merge_with_value
+ end
+
+ # internal_set bypasses converting keys, does convert values and allows writing to immutable mashes
+ merge_onto.internal_set(key, value)
+ end
+ merge_onto
+
+ # If merge_with is NIL, don't replace merge_onto
+ elsif merge_with == NIL
+ merge_onto
+
+ # In all other cases, replace merge_onto with merge_with
+ else
+ if merge_with.is_a?(Hash)
+ Chef::Node::ImmutableMash.new(merge_with)
+ elsif merge_with.is_a?(Array)
+ Chef::Node::ImmutableArray.new(merge_with)
+ else
+ merge_with
+ end
+ end
+ end
+ end
end
end
diff --git a/lib/chef/node/attribute_collections.rb b/lib/chef/node/attribute_collections.rb
index b739ea8490..a420563165 100644
--- a/lib/chef/node/attribute_collections.rb
+++ b/lib/chef/node/attribute_collections.rb
@@ -1,6 +1,6 @@
#--
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,7 +16,11 @@
# limitations under the License.
#
-require "chef/node/common_api"
+require_relative "common_api"
+require_relative "mixin/state_tracking"
+require_relative "mixin/immutablize_array"
+require_relative "mixin/immutablize_hash"
+require_relative "mixin/mashy_array"
class Chef
class Node
@@ -25,37 +29,9 @@ class Chef
# "root" (Chef::Node::Attribute) object, and will trigger a cache
# invalidation on that object when mutated.
class AttrArray < Array
- MUTATOR_METHODS = [
- :<<,
- :[]=,
- :clear,
- :collect!,
- :compact!,
- :default=,
- :default_proc=,
- :delete,
- :delete_at,
- :delete_if,
- :fill,
- :flatten!,
- :insert,
- :keep_if,
- :map!,
- :merge!,
- :pop,
- :push,
- :update,
- :reject!,
- :reverse!,
- :replace,
- :select!,
- :shift,
- :slice!,
- :sort!,
- :sort_by!,
- :uniq!,
- :unshift,
- ]
+ include Chef::Node::Mixin::MashyArray
+
+ MUTATOR_METHODS = Chef::Node::Mixin::ImmutablizeArray::DISALLOWED_MUTATOR_METHODS
# For all of the methods that may mutate an Array, we override them to
# also invalidate the cached merged_attributes on the root
@@ -63,16 +39,19 @@ class Chef
MUTATOR_METHODS.each do |mutator|
define_method(mutator) do |*args, &block|
ret = super(*args, &block)
- root.reset_cache(root.top_level_breadcrumb)
+ send_reset_cache
ret
end
end
- attr_reader :root
+ def delete(key, &block)
+ send_reset_cache(__path__, key)
+ super
+ end
- def initialize(root, data)
- @root = root
+ def initialize(data = [])
super(data)
+ map! { |e| convert_value(e) }
end
# For elements like Fixnums, true, nil...
@@ -86,6 +65,31 @@ class Chef
Array.new(map { |e| safe_dup(e) })
end
+ def to_yaml(*opts)
+ to_a.to_yaml(*opts)
+ end
+
+ private
+
+ def convert_value(value)
+ case value
+ when VividMash, AttrArray
+ value
+ when Hash
+ VividMash.new(value, __root__, __node__, __precedence__)
+ when Array
+ AttrArray.new(value, __root__, __node__, __precedence__)
+ else
+ value
+ end
+ end
+
+ # needed for __path__
+ def convert_key(key)
+ key
+ end
+
+ prepend Chef::Node::Mixin::StateTracking
end
# == VividMash
@@ -99,46 +103,36 @@ class Chef
# #fetch, work as normal).
# * attr_accessor style element set and get are supported via method_missing
class VividMash < Mash
- attr_reader :root
-
include CommonAPI
# Methods that mutate a VividMash. Each of them is overridden so that it
# also invalidates the cached merged_attributes on the root Attribute
# object.
- MUTATOR_METHODS = [
- :clear,
- :delete,
- :delete_if,
- :keep_if,
- :merge!,
- :update,
- :reject!,
- :replace,
- :select!,
- :shift,
- ]
+ MUTATOR_METHODS = Chef::Node::Mixin::ImmutablizeHash::DISALLOWED_MUTATOR_METHODS - %i{write write! unlink unlink!}
# For all of the mutating methods on Mash, override them so that they
# also invalidate the cached `merged_attributes` on the root Attribute
# object.
MUTATOR_METHODS.each do |mutator|
define_method(mutator) do |*args, &block|
- root.reset_cache(root.top_level_breadcrumb)
+ send_reset_cache
super(*args, &block)
end
end
- def initialize(root, data = {})
- @root = root
+ def delete(key, &block)
+ send_reset_cache(__path__, key)
+ super
+ end
+
+ def initialize(data = {})
super(data)
end
def [](key)
- root.top_level_breadcrumb ||= key
value = super
if !key?(key)
- value = self.class.new(root)
+ value = self.class.new({}, __root__)
self[key] = value
else
value
@@ -146,32 +140,13 @@ class Chef
end
def []=(key, value)
- root.top_level_breadcrumb ||= key
ret = super
- root.reset_cache(root.top_level_breadcrumb)
- ret
+ send_reset_cache(__path__, key)
+ ret # rubocop:disable Lint/Void
end
alias :attribute? :has_key?
- def method_missing(symbol, *args)
- # Calling `puts arg` implicitly calls #to_ary on `arg`. If `arg` does
- # not implement #to_ary, ruby recognizes it as a single argument, and
- # if it returns an Array, then ruby prints each element. If we don't
- # account for that here, we'll auto-vivify a VividMash for the key
- # :to_ary which creates an unwanted key and raises a TypeError.
- if symbol == :to_ary
- super
- elsif args.empty?
- self[symbol]
- elsif symbol.to_s =~ /=$/
- key_to_set = symbol.to_s[/^(.+)=$/, 1]
- self[key_to_set] = (args.length == 1 ? args[0] : args)
- else
- raise NoMethodError, "Undefined node attribute or method `#{symbol}' on `node'. To set an attribute, use `#{symbol}=value' instead."
- end
- end
-
def convert_key(key)
super
end
@@ -182,12 +157,12 @@ class Chef
# attribute tree will have the correct cache invalidation behavior.
def convert_value(value)
case value
- when VividMash
+ when VividMash, AttrArray
value
when Hash
- VividMash.new(root, value)
+ VividMash.new(value, __root__, __node__, __precedence__)
when Array
- AttrArray.new(root, value)
+ AttrArray.new(value, __root__, __node__, __precedence__)
else
value
end
@@ -197,6 +172,11 @@ class Chef
Mash.new(self)
end
+ def to_yaml(*opts)
+ to_h.to_yaml(*opts)
+ end
+
+ prepend Chef::Node::Mixin::StateTracking
end
end
end
diff --git a/lib/chef/node/common_api.rb b/lib/chef/node/common_api.rb
index ce2c6b6878..b711d4ee95 100644
--- a/lib/chef/node/common_api.rb
+++ b/lib/chef/node/common_api.rb
@@ -1,5 +1,5 @@
#--
-# Copyright:: Copyright 2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,7 +24,7 @@ class Chef
# method-style access to attributes
def valid_container?(obj, key)
- return obj.is_a?(Hash) || (obj.is_a?(Array) && key.is_a?(Fixnum))
+ obj.is_a?(Hash) || (obj.is_a?(Array) && key.is_a?(Integer))
end
private :valid_container?
@@ -32,12 +32,11 @@ class Chef
# - autovivifying / autoreplacing writer
# - non-container-ey intermediate objects are replaced with hashes
def write(*args, &block)
- root.top_level_breadcrumb = nil if respond_to?(:root)
value = block_given? ? yield : args.pop
last = args.pop
prev_memo = prev_key = nil
chain = args.inject(self) do |memo, key|
- if !valid_container?(memo, key)
+ unless valid_container?(memo, key)
prev_memo[prev_key] = {}
memo = prev_memo[prev_key]
end
@@ -45,7 +44,7 @@ class Chef
prev_key = key
memo[key]
end
- if !valid_container?(chain, last)
+ unless valid_container?(chain, last)
prev_memo[prev_key] = {}
chain = prev_memo[prev_key]
end
@@ -56,14 +55,15 @@ class Chef
# something that is not a container ("schema violation" issues).
#
def write!(*args, &block)
- root.top_level_breadcrumb = nil if respond_to?(:root)
value = block_given? ? yield : args.pop
last = args.pop
obj = args.inject(self) do |memo, key|
raise Chef::Exceptions::AttributeTypeMismatch unless valid_container?(memo, key)
+
memo[key]
end
raise Chef::Exceptions::AttributeTypeMismatch unless valid_container?(obj, last)
+
obj[last] = value
end
@@ -71,9 +71,9 @@ class Chef
# return true or false based on if the attribute exists
def exist?(*path)
- root.top_level_breadcrumb = nil if respond_to?(:root)
path.inject(self) do |memo, key|
return false unless valid_container?(memo, key)
+
if memo.is_a?(Hash)
if memo.key?(key)
memo[key]
@@ -88,22 +88,22 @@ class Chef
end
end
end
- return true
+ true
end
# this is a safe non-autovivifying reader that returns nil if the attribute does not exist
def read(*path)
- begin
- read!(*path)
- rescue Chef::Exceptions::NoSuchAttribute
- nil
- end
+ read!(*path)
+ rescue Chef::Exceptions::NoSuchAttribute
+ nil
end
+ alias :dig :read
+
# non-autovivifying reader that throws an exception if the attribute does not exist
def read!(*path)
- raise Chef::Exceptions::NoSuchAttribute unless exist?(*path)
- root.top_level_breadcrumb = nil if respond_to?(:root)
+ raise Chef::Exceptions::NoSuchAttribute.new(path.join ".") unless exist?(*path)
+
path.inject(self) do |memo, key|
memo[key]
end
@@ -112,15 +112,15 @@ class Chef
# FIXME:(?) does anyone really like the autovivifying reader that we have and wants the same behavior? readers that write? ugh...
def unlink(*path, last)
- root.top_level_breadcrumb = nil if respond_to?(:root)
hash = path.empty? ? self : read(*path)
return nil unless hash.is_a?(Hash) || hash.is_a?(Array)
- root.top_level_breadcrumb ||= last
+
hash.delete(last)
end
def unlink!(*path)
raise Chef::Exceptions::NoSuchAttribute unless exist?(*path)
+
unlink(*path)
end
diff --git a/lib/chef/node/immutable_collections.rb b/lib/chef/node/immutable_collections.rb
index d4623ace2a..49dc0256b9 100644
--- a/lib/chef/node/immutable_collections.rb
+++ b/lib/chef/node/immutable_collections.rb
@@ -1,5 +1,5 @@
#--
-# Copyright:: Copyright 2012-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,22 +15,37 @@
# limitations under the License.
#
-require "chef/node/common_api"
+require_relative "common_api"
+require_relative "mixin/state_tracking"
+require_relative "mixin/immutablize_array"
+require_relative "mixin/immutablize_hash"
class Chef
class Node
-
module Immutablize
- def immutablize(value)
+ # For elements like Fixnums, true, nil...
+ def safe_dup(e)
+ e.dup
+ rescue TypeError
+ e
+ end
+
+ def convert_value(value)
case value
when Hash
- ImmutableMash.new(value)
+ ImmutableMash.new(value, __root__, __node__, __precedence__)
when Array
- ImmutableArray.new(value)
- else
+ ImmutableArray.new(value, __root__, __node__, __precedence__)
+ when ImmutableMash, ImmutableArray
value
+ else
+ safe_dup(value).freeze
end
end
+
+ def immutablize(value)
+ convert_value(value)
+ end
end
# == ImmutableArray
@@ -49,55 +64,12 @@ class Chef
alias :internal_push :<<
private :internal_push
- # A list of methods that mutate Array. Each of these is overridden to
- # raise an error, making this instances of this class more or less
- # immutable.
- DISALLOWED_MUTATOR_METHODS = [
- :<<,
- :[]=,
- :clear,
- :collect!,
- :compact!,
- :default=,
- :default_proc=,
- :delete,
- :delete_at,
- :delete_if,
- :fill,
- :flatten!,
- :insert,
- :keep_if,
- :map!,
- :merge!,
- :pop,
- :push,
- :update,
- :reject!,
- :reverse!,
- :replace,
- :select!,
- :shift,
- :slice!,
- :sort!,
- :sort_by!,
- :uniq!,
- :unshift,
- ]
-
- def initialize(array_data)
+ def initialize(array_data = [])
array_data.each do |value|
internal_push(immutablize(value))
end
end
- # Redefine all of the methods that mutate a Hash to raise an error when called.
- # This is the magic that makes this object "Immutable"
- DISALLOWED_MUTATOR_METHODS.each do |mutator_method_name|
- define_method(mutator_method_name) do |*args, &block|
- raise Exceptions::ImmutableAttributeModification
- end
- end
-
# For elements like Fixnums, true, nil...
def safe_dup(e)
e.dup
@@ -110,21 +82,35 @@ class Chef
end
def to_a
- a = Array.new
- each do |v|
- a <<
- case v
- when ImmutableArray
- v.to_a
- when ImmutableMash
- v.to_hash
- else
- v
- end
- end
- a
+ Array.new(map do |v|
+ case v
+ when ImmutableArray
+ v.to_a
+ when ImmutableMash
+ v.to_h
+ else
+ safe_dup(v)
+ end
+ end)
+ end
+
+ alias_method :to_array, :to_a
+
+ # As Psych module, not respecting ImmutableArray object
+ # So first convert it to Hash/Array then parse it to `.to_yaml`
+ def to_yaml(*opts)
+ to_a.to_yaml(*opts)
+ end
+
+ private
+
+ # needed for __path__
+ def convert_key(key)
+ key
end
+ prepend Chef::Node::Mixin::StateTracking
+ prepend Chef::Node::Mixin::ImmutablizeArray
end
# == ImmutableMash
@@ -134,84 +120,28 @@ class Chef
# ImmutableMash acts like a Mash (Hash that is indifferent to String or
# Symbol keys), with some important exceptions:
# * Methods that mutate state are overridden to raise an error instead.
- # * Methods that read from the collection are overriden so that they check
+ # * Methods that read from the collection are overridden so that they check
# if the Chef::Node::Attribute has been modified since an instance of
# this class was generated. An error is raised if the object detects that
# it is stale.
# * Values can be accessed in attr_reader-like fashion via method_missing.
class ImmutableMash < Mash
-
include Immutablize
include CommonAPI
- alias :internal_set :[]=
- private :internal_set
-
- DISALLOWED_MUTATOR_METHODS = [
- :[]=,
- :clear,
- :collect!,
- :default=,
- :default_proc=,
- :delete,
- :delete_if,
- :keep_if,
- :map!,
- :merge!,
- :update,
- :reject!,
- :replace,
- :select!,
- :shift,
- :write,
- :write!,
- :unlink,
- :unlink!,
- ]
-
- def initialize(mash_data)
- mash_data.each do |key, value|
- internal_set(key, immutablize(value))
- end
- end
-
- def public_method_that_only_deep_merge_should_use(key, value)
- internal_set(key, immutablize(value))
- end
-
- alias :attribute? :has_key?
-
- # Redefine all of the methods that mutate a Hash to raise an error when called.
- # This is the magic that makes this object "Immutable"
- DISALLOWED_MUTATOR_METHODS.each do |mutator_method_name|
- define_method(mutator_method_name) do |*args, &block|
- raise Exceptions::ImmutableAttributeModification
- end
+ # this is for deep_merge usage, chef users must never touch this API
+ # @api private
+ def internal_set(key, value)
+ regular_writer(key, convert_value(value))
end
- def method_missing(symbol, *args)
- if symbol == :to_ary
- super
- elsif args.empty?
- if key?(symbol)
- self[symbol]
- else
- raise NoMethodError, "Undefined method or attribute `#{symbol}' on `node'"
- end
- # This will raise a ImmutableAttributeModification error:
- elsif symbol.to_s =~ /=$/
- key_to_set = symbol.to_s[/^(.+)=$/, 1]
- self[key_to_set] = (args.length == 1 ? args[0] : args)
- else
- raise NoMethodError, "Undefined node attribute or method `#{symbol}' on `node'"
+ def initialize(mash_data = {})
+ mash_data.each do |key, value|
+ internal_set(key, value)
end
end
- # Mash uses #convert_value to mashify values on input.
- # Since we're handling this ourselves, override it to be a no-op
- def convert_value(value)
- value
- end
+ alias :attribute? :has_key?
# NOTE: #default and #default= are likely to be pretty confusing. For a
# regular ruby Hash, they control what value is returned for, e.g.,
@@ -219,26 +149,46 @@ class Chef
# Of course, 'default' has a specific meaning in Chef-land
def dup
- Mash.new(self)
+ h = Mash.new
+ each_pair do |k, v|
+ h[k] = safe_dup(v)
+ end
+ h
end
- def to_hash
- h = Hash.new
+ def to_h
+ h = {}
each_pair do |k, v|
h[k] =
case v
when ImmutableMash
- v.to_hash
+ v.to_h
when ImmutableArray
v.to_a
else
- v
+ safe_dup(v)
end
end
h
end
- end
+ alias_method :to_hash, :to_h
+
+ # As Psych module, not respecting ImmutableMash object
+ # So first convert it to Hash/Array then parse it to `.to_yaml`
+ def to_yaml(*opts)
+ to_h.to_yaml(*opts)
+ end
+ # For elements like Fixnums, true, nil...
+ def safe_dup(e)
+ e.dup
+ rescue TypeError
+ e
+ end
+
+ prepend Chef::Node::Mixin::StateTracking
+ prepend Chef::Node::Mixin::ImmutablizeHash
+ end
end
end
diff --git a/lib/chef/node/mixin/deep_merge_cache.rb b/lib/chef/node/mixin/deep_merge_cache.rb
new file mode 100644
index 0000000000..e88e079895
--- /dev/null
+++ b/lib/chef/node/mixin/deep_merge_cache.rb
@@ -0,0 +1,61 @@
+#--
+# Copyright:: Copyright (c) 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 Node
+ module Mixin
+ module DeepMergeCache
+ # Cache of deep merged values by top-level key. This is a simple hash which has keys that are the
+ # top-level keys of the node object, and we save the computed deep-merge for that key here. There is
+ # no cache of subtrees.
+ attr_accessor :deep_merge_cache
+
+ def initialize
+ @merged_attributes = nil
+ @combined_override = nil
+ @combined_default = nil
+ @deep_merge_cache = {}
+ end
+
+ # Invalidate a key in the deep_merge_cache. If called with nil, or no arg, this will invalidate
+ # the entire deep_merge cache. In the case of the user doing node.default['foo']['bar']['baz']=
+ # that eventually results in a call to reset_cache('foo') here. A node.default=hash_thing call
+ # must invalidate the entire cache and re-deep-merge the entire node object.
+ def reset_cache(path = nil)
+ if path.nil?
+ deep_merge_cache.clear
+ else
+ deep_merge_cache.delete(path.to_s)
+ end
+ end
+
+ alias :reset :reset_cache
+
+ def [](key)
+ if deep_merge_cache.key?(key.to_s)
+ # return the cache of the deep merged values by top-level key
+ deep_merge_cache[key.to_s]
+ else
+ # save all the work of computing node[key]
+ deep_merge_cache[key.to_s] = merged_attributes(key)
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/node/mixin/immutablize_array.rb b/lib/chef/node/mixin/immutablize_array.rb
new file mode 100644
index 0000000000..652061bc36
--- /dev/null
+++ b/lib/chef/node/mixin/immutablize_array.rb
@@ -0,0 +1,184 @@
+#--
+# Copyright:: Copyright (c) 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 Node
+ module Mixin
+ module ImmutablizeArray
+ # Allowed methods that MUST NOT mutate the object
+ # (if any of these methods mutate the underlying object that is a bug that needs to be fixed)
+ ALLOWED_METHODS = %i{
+ &
+ *
+ +
+ -
+ []
+ abbrev
+ all?
+ any?
+ assoc
+ at
+ bsearch
+ bsearch_index
+ chain
+ chunk
+ chunk_while
+ collect
+ collect_concat
+ combination
+ compact
+ count
+ cycle
+ deconstruct
+ detect
+ difference
+ dig
+ drop
+ drop_while
+ each
+ each_cons
+ each_entry
+ each_index
+ each_slice
+ each_with_index
+ each_with_object
+ empty?
+ entries
+ fetch
+ filter
+ filter_map
+ find
+ find_all
+ find_index
+ first
+ flat_map
+ flatten
+ grep
+ grep_v
+ group_by
+ include?
+ index
+ inject
+ intersection
+ join
+ last
+ lazy
+ length
+ map
+ max
+ max_by
+ member?
+ min
+ min_by
+ minmax
+ minmax_by
+ none?
+ one?
+ pack
+ partition
+ permutation
+ product
+ rassoc
+ reduce
+ reject
+ repeated_combination
+ repeated_permutation
+ reverse
+ reverse_each
+ rindex
+ rotate
+ sample
+ save_plist
+ select
+ shelljoin
+ shuffle
+ size
+ slice
+ slice_after
+ slice_before
+ slice_when
+ sort
+ sort_by
+ sum
+ take
+ take_while
+ tally
+ to_a
+ to_ary
+ to_csv
+ to_h
+ to_plist
+ to_set
+ to_yaml
+ transpose
+ union
+ uniq
+ values_at
+ zip
+ |
+ }.freeze
+ # A list of methods that mutate Array. Each of these is overridden to
+ # raise an error, making this instances of this class more or less
+ # immutable.
+ DISALLOWED_MUTATOR_METHODS = %i{
+ <<
+ []=
+ append
+ clear
+ collect!
+ compact!
+ concat
+ default=
+ default_proc=
+ delete
+ delete_at
+ delete_if
+ fill
+ filter!
+ flatten!
+ insert
+ keep_if
+ map!
+ merge!
+ pop
+ prepend
+ push
+ reject!
+ replace
+ reverse!
+ rotate!
+ select!
+ shift
+ shuffle!
+ slice!
+ sort!
+ sort_by!
+ uniq!
+ unshift
+ }.freeze
+
+ # Redefine all of the methods that mutate a Hash to raise an error when called.
+ # This is the magic that makes this object "Immutable"
+ DISALLOWED_MUTATOR_METHODS.each do |mutator_method_name|
+ define_method(mutator_method_name) do |*args, &block|
+ raise Exceptions::ImmutableAttributeModification
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/node/mixin/immutablize_hash.rb b/lib/chef/node/mixin/immutablize_hash.rb
new file mode 100644
index 0000000000..f6d885ca2b
--- /dev/null
+++ b/lib/chef/node/mixin/immutablize_hash.rb
@@ -0,0 +1,171 @@
+#--
+# Copyright:: Copyright (c) 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 Node
+ module Mixin
+ module ImmutablizeHash
+ # allowed methods that MUST NOT mutate the object
+ # (if any of these methods mutate the underlying object that is a bug that needs to be fixed)
+ ALLOWED_METHODS = %i{
+ <
+ <=
+ >
+ >=
+ []
+ all?
+ any?
+ assoc
+ chain
+ chunk
+ chunk_while
+ collect
+ collect_concat
+ compact
+ compare_by_identity
+ compare_by_identity?
+ count
+ cycle
+ deconstruct_keys
+ default
+ default_proc
+ detect
+ dig
+ drop
+ drop_while
+ each
+ each_cons
+ each_entry
+ each_key
+ each_pair
+ each_slice
+ each_value
+ each_with_index
+ each_with_object
+ empty?
+ entries
+ except
+ fetch
+ fetch_values
+ filter
+ filter_map
+ find
+ find_all
+ find_index
+ first
+ flat_map
+ flatten
+ grep
+ grep_v
+ group_by
+ has_key?
+ has_value?
+ include?
+ index
+ inject
+ invert
+ key
+ key?
+ keys
+ lazy
+ length
+ map
+ max
+ max_by
+ member?
+ merge
+ min
+ min_by
+ minmax
+ minmax_by
+ none?
+ normalize_param
+ one?
+ partition
+ rassoc
+ reduce
+ reject
+ reverse_each
+ save_plist
+ select
+ size
+ slice
+ slice_after
+ slice_before
+ slice_when
+ sort
+ sort_by
+ sum
+ take
+ take_while
+ tally
+ to_a
+ to_h
+ to_hash
+ to_plist
+ to_proc
+ to_set
+ to_xml_attributes
+ to_yaml
+ transform_keys
+ transform_values
+ uniq
+ value?
+ values
+ values_at
+ zip
+ }.freeze
+ DISALLOWED_MUTATOR_METHODS = %i{
+ []=
+ clear
+ collect!
+ compact!
+ default=
+ default_proc=
+ delete
+ delete_if
+ except!
+ filter!
+ keep_if
+ map!
+ merge!
+ rehash
+ reject!
+ replace
+ select!
+ shift
+ store
+ transform_keys!
+ transform_values!
+ unlink!
+ unlink
+ update
+ write!
+ write
+ }.freeze
+
+ # Redefine all of the methods that mutate a Hash to raise an error when called.
+ # This is the magic that makes this object "Immutable"
+ DISALLOWED_MUTATOR_METHODS.each do |mutator_method_name|
+ define_method(mutator_method_name) do |*args, &block|
+ raise Exceptions::ImmutableAttributeModification
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/node/mixin/mashy_array.rb b/lib/chef/node/mixin/mashy_array.rb
new file mode 100644
index 0000000000..55aedf4779
--- /dev/null
+++ b/lib/chef/node/mixin/mashy_array.rb
@@ -0,0 +1,68 @@
+#--
+# Copyright:: Copyright (c) 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 Node
+ module Mixin
+ # missing methods for Arrays similar to Chef::Mash methods that call
+ # convert_value correctly.
+ module MashyArray
+ def <<(obj)
+ super(convert_value(obj))
+ end
+
+ def []=(*keys, value)
+ super(*keys, convert_value(value))
+ end
+
+ def push(*objs)
+ objs = objs.map { |obj| convert_value(obj) }
+ super(*objs)
+ end
+
+ def unshift(*objs)
+ objs = objs.map { |obj| convert_value(obj) }
+ super(*objs)
+ end
+
+ def insert(index, *objs)
+ objs = objs.map { |obj| convert_value(obj) }
+ super(index, *objs)
+ end
+
+ def collect!(&block)
+ super
+ map! { |x| convert_value(x) }
+ end
+
+ def map!(&block)
+ super
+ super { |x| convert_value(x) }
+ end
+
+ def fill(*args, &block)
+ super
+ map! { |x| convert_value(x) }
+ end
+
+ def replace(obj)
+ super(convert_value(obj))
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/node/mixin/state_tracking.rb b/lib/chef/node/mixin/state_tracking.rb
new file mode 100644
index 0000000000..4d197e7cbd
--- /dev/null
+++ b/lib/chef/node/mixin/state_tracking.rb
@@ -0,0 +1,96 @@
+#--
+# Copyright:: Copyright (c) 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 Node
+ module Mixin
+ module StateTracking
+ attr_reader :__path__
+ attr_reader :__root__
+ attr_reader :__node__
+ attr_reader :__precedence__
+
+ def initialize(data = nil, root = self, node = nil, precedence = nil)
+ # __path__ and __root__ must be nil when we call super so it knows
+ # to avoid resetting the cache on construction
+ data.nil? ? super() : super(data)
+ @__path__ = []
+ @__root__ = root
+ @__node__ = node
+ @__precedence__ = precedence
+ end
+
+ def [](*args)
+ ret = super
+ key = args.first
+ next_path = [ __path__, convert_key(key) ].flatten.compact
+ copy_state_to(ret, next_path)
+ end
+
+ def []=(*args)
+ ret = super
+ key = args.first
+ value = args.last
+ next_path = [ __path__, convert_key(key) ].flatten.compact
+ send_attribute_changed_event(next_path, value)
+ copy_state_to(ret, next_path)
+ end
+
+ protected
+
+ def __path__=(path)
+ @__path__ = path
+ end
+
+ def __root__=(root)
+ @__root__ = root
+ end
+
+ def __precedence__=(precedence)
+ @__precedence__ = precedence
+ end
+
+ def __node__=(node)
+ @__node__ = node
+ end
+
+ private
+
+ def send_attribute_changed_event(next_path, value)
+ if __node__ && __node__.run_context && __node__.run_context.events
+ __node__.run_context.events.attribute_changed(__precedence__, next_path, value)
+ end
+ end
+
+ def send_reset_cache(path = nil, key = nil)
+ next_path = [ path, key ].flatten.compact
+ __root__.reset_cache(next_path.first) if !__root__.nil? && __root__.respond_to?(:reset_cache) && !next_path.nil?
+ end
+
+ def copy_state_to(ret, next_path)
+ if ret.is_a?(StateTracking)
+ ret.__path__ = next_path
+ ret.__root__ = __root__
+ ret.__node__ = __node__
+ ret.__precedence__ = __precedence__
+ end
+ ret
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/node_map.rb b/lib/chef/node_map.rb
index 5eac63d380..0b85dbe9df 100644
--- a/lib/chef/node_map.rb
+++ b/lib/chef/node_map.rb
@@ -1,6 +1,6 @@
#
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,8 +16,34 @@
# limitations under the License.
#
+#
+# example of a NodeMap entry for the user resource (as typed on the DSL):
+#
+# :user=>
+# [{:klass=>Chef::Resource::User::AixUser, :os=>"aix"},
+# {:klass=>Chef::Resource::User::DsclUser, :os=>"darwin"},
+# {:klass=>Chef::Resource::User::PwUser, :os=>"freebsd"},
+# {:klass=>Chef::Resource::User::LinuxUser, :os=>"linux"},
+# {:klass=>Chef::Resource::User::SolarisUser,
+# :os=>["omnios", "solaris2"]},
+# {:klass=>Chef::Resource::User::WindowsUser, :os=>"windows"}],
+#
+# the entries in the array are pre-sorted into priority order (blocks/platform_version/platform/platform_family/os/none) so that
+# the first entry's :klass that matches the filter is returned when doing a get.
+#
+# note that as this examples show filter values may be a scalar string or an array of scalar strings.
+#
+# XXX: confusingly, in the *_priority_map the :klass may be an array of Strings of class names
+#
+
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
class Chef
class NodeMap
+ COLLISION_WARNING = <<~EOH.gsub(/\s+/, " ").strip
+ %{type_caps} %{key} built into %{client_name} is being overridden by the %{type} from a cookbook. Please upgrade your cookbook
+ or remove the cookbook from your run_list.
+ EOH
#
# Set a key/value pair on the map with a filter. The filter must be true
@@ -26,24 +52,42 @@ class Chef
# @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
+ # @param chef_version [String] version constraint to match against the running Chef::VERSION
#
# @yield [node] Arbitrary node filter as a block which takes a node argument
#
# @return [NodeMap] Returns self for possible chaining
#
- def set(key, value, platform: nil, platform_version: nil, platform_family: nil, os: nil, on_platform: nil, on_platforms: nil, canonical: nil, 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 }
+ def set(key, klass, platform: nil, platform_version: nil, platform_family: nil, os: nil, override: nil, chef_version: nil, target_mode: nil, &block)
+ new_matcher = { klass: klass }
+ new_matcher[:platform] = platform if platform
+ new_matcher[:platform_version] = platform_version if platform_version
+ new_matcher[:platform_family] = platform_family if platform_family
+ new_matcher[:os] = os if os
new_matcher[:block] = block if block
- new_matcher[:canonical] = canonical if canonical
new_matcher[:override] = override if override
+ new_matcher[:target_mode] = target_mode
+
+ if chef_version && Chef::VERSION !~ chef_version
+ return map
+ end
+
+ # Check if the key is already present and locked, unless the override is allowed.
+ # The checks to see if we should reject, in order:
+ # 1. Core override mode is not set.
+ # 2. The key exists.
+ # 3. At least one previous `provides` is now locked.
+ if map[key] && map[key].any? { |matcher| matcher[:locked] } && !map[key].any? { |matcher| matcher[:cookbook_override].is_a?(String) ? Chef::VERSION =~ matcher[:cookbook_override] : matcher[:cookbook_override] }
+ # If we ever use locked mode on things other than the resource and provider handler maps, this probably needs a tweak.
+ type_of_thing = if klass < Chef::Resource
+ "resource"
+ elsif klass < Chef::Provider
+ "provider"
+ else
+ klass.superclass.to_s
+ end
+ Chef::Log.warn( COLLISION_WARNING % { type: type_of_thing, key: key, type_caps: type_of_thing.capitalize, client_name: ChefUtils::Dist::Infra::PRODUCT } )
+ end
# 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).
@@ -51,7 +95,10 @@ class Chef
map[key] ||= []
map[key].each_with_index do |matcher, index|
cmp = compare_matchers(key, new_matcher, matcher)
- insert_at ||= index if cmp && cmp <= 0
+ if cmp && cmp <= 0
+ insert_at = index
+ break
+ end
end
if insert_at
map[key].insert(insert_at, new_matcher)
@@ -68,14 +115,16 @@ class Chef
# @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
+ # @return [Object] Class
#
- 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
+ def get(node, key)
+ return nil unless map.key?(key)
+
+ map[key].map do |matcher|
+ return matcher[:klass] if node_matches?(node, matcher)
+ end
+ nil
end
#
@@ -85,134 +134,194 @@ class Chef
# @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
+ # @return [Object] Class
#
- 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)
+ def list(node, key)
+ return [] unless map.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
+ node_matches?(node, matcher)
+ end.map { |matcher| matcher[:klass] }
+ end
+
+ # Remove a class from all its matchers in the node_map, will remove mappings completely if its the last matcher left
+ #
+ # Note that this leaks the internal structure out a bit, but the main consumer of this (poise/halite) cares only about
+ # the keys in the returned Hash.
+ #
+ # @param klass [Class] the class to seek and destroy
+ #
+ # @return [Hash] deleted entries in the same format as the @map
+ def delete_class(klass)
+ raise "please use a Class type for the klass argument" unless klass.is_a?(Class)
+
+ deleted = {}
+ map.each do |key, matchers|
+ deleted_matchers = []
+ matchers.delete_if do |matcher|
+ # because matcher[:klass] may be a string (which needs to die), coerce both to strings to compare somewhat canonically
+ if matcher[:klass].to_s == klass.to_s
+ deleted_matchers << matcher
+ true
+ end
+ end
+ deleted[key] = deleted_matchers unless deleted_matchers.empty?
+ map.delete(key) if matchers.empty?
+ end
+ deleted
+ end
+
+ # Check if this map has been locked.
+ #
+ # @api internal
+ # @since 14.2
+ # @return [Boolean]
+ def locked?
+ if defined?(@locked)
+ @locked
+ else
+ false
+ end
+ end
+
+ # Set this map to locked mode. This is used to prevent future overwriting
+ # of existing names.
+ #
+ # @api internal
+ # @since 14.2
+ # @return [void]
+ def lock!
+ map.each do |key, matchers|
+ matchers.each do |matcher|
+ matcher[:locked] = true
end
end
- remaining
+ @locked = true
end
- protected
+ private
+
+ def platform_family_query_helper?(node, m)
+ method = "#{m}?".to_sym
+ ChefUtils::DSL::PlatformFamily.respond_to?(method) && ChefUtils::DSL::PlatformFamily.send(method, node)
+ 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)
+ def matches_block_allow_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]
+ return true unless 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?("!") }
+ # Split the blocklist and allowlist
+ blocklist, allowlist = 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 attribute == :platform_family
+ # If any blocklist value matches, we don't match
+ return false if blocklist.any? { |v| v[1..] == value || platform_family_query_helper?(node, v[1..]) }
- # If the whitelist is empty, or anything matches, we match.
- whitelist.empty? || whitelist.any? { |v| v == :all || v == value }
+ # If the allowlist is empty, or anything matches, we match.
+ allowlist.empty? || allowlist.any? { |v| v == :all || v == value || platform_family_query_helper?(node, v) }
+ else
+ # If any blocklist value matches, we don't match
+ return false if blocklist.any? { |v| v[1..] == value }
+
+ # If the allowlist is empty, or anything matches, we match.
+ allowlist.empty? || allowlist.any? { |v| v == :all || v == value }
+ end
end
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]
+ return true unless 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)
+ Gem::Requirement.new(v).satisfied_by?(Gem::Version.new(value))
end
end
+ # Succeeds if:
+ # - we are in target mode, and the target_mode filter is true
+ # - we are not in target mode
+ #
+ def matches_target_mode?(filters)
+ return true unless Chef::Config.target_mode?
+
+ !!filters[:target_mode]
+ end
+
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)
+ matches_block_allow_list?(node, filters, :os) &&
+ matches_block_allow_list?(node, filters, :platform_family) &&
+ matches_block_allow_list?(node, filters, :platform) &&
+ matches_version_list?(node, filters, :platform_version) &&
+ matches_target_mode?(filters)
end
def block_matches?(node, block)
return true if block.nil?
+
block.call node
end
def node_matches?(node, matcher)
- return true if !node
- filters_match?(node, matcher[:filters]) && block_matches?(node, matcher[:block])
- end
+ return true unless node
- def canonical_matches?(canonical, matcher)
- return true if canonical.nil?
- !!canonical == !!matcher[:canonical]
+ filters_match?(node, matcher) && block_matches?(node, matcher[:block])
end
+ #
+ # "provides" lines with identical filters sort by class name (ascending).
+ #
def compare_matchers(key, new_matcher, matcher)
- cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:block] }
+ cmp = compare_matcher_properties(new_matcher[:block], matcher[:block])
return cmp if cmp != 0
- cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform_version] }
+
+ cmp = compare_matcher_properties(new_matcher[:platform_version], matcher[:platform_version])
return cmp if cmp != 0
- cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform] }
+
+ cmp = compare_matcher_properties(new_matcher[:platform], matcher[:platform])
return cmp if cmp != 0
- cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform_family] }
+
+ cmp = compare_matcher_properties(new_matcher[:platform_family], matcher[:platform_family])
return cmp if cmp != 0
- cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:os] }
+
+ cmp = compare_matcher_properties(new_matcher[:os], matcher[:os])
return cmp if cmp != 0
- cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:override] }
+
+ cmp = compare_matcher_properties(new_matcher[:override], matcher[:override])
return cmp if cmp != 0
+
# If all things are identical, return 0
0
end
- def compare_matcher_properties(new_matcher, matcher)
- a = yield(new_matcher)
- b = yield(matcher)
+ def compare_matcher_properties(a, b)
+ # falsity comparisons here handle both "nil" and "false"
+ return 1 if !a && b
+ return -1 if !b && a
+ return 0 if !a && !b
- # Check for blcacklists ('!windows'). Those always come *after* positive
- # whitelists.
+ # Check for blocklists ('!windows'). Those always come *after* positive
+ # allowlists.
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
+ return 1 if a_negated && !b_negated
+ return -1 if b_negated && !a_negated
- # We treat false / true and nil / not-nil with the same comparison
- a = nil if a == false
- b = nil if b == false
- cmp = a <=> b
- # This is the case where one is non-nil, and one is nil. The one that is
- # nil is "greater" (i.e. it should come last).
- if cmp.nil?
- return 1 if a.nil?
- return -1 if b.nil?
- end
- cmp
+ a <=> b
end
def map
diff --git a/lib/chef/null_logger.rb b/lib/chef/null_logger.rb
index 927cfc0c08..8b00b3c03c 100644
--- a/lib/chef/null_logger.rb
+++ b/lib/chef/null_logger.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2014-2016, Chef Software, Inc
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -27,26 +27,21 @@ class Chef
# probably expected a real logger and not this "fake" one.
class NullLogger
- def fatal(message, &block)
- end
+ def fatal(message, &block); end
- def error(message, &block)
- end
+ def error(message, &block); end
- def warn(message, &block)
- end
+ def warn(message, &block); end
- def info(message, &block)
- end
+ def info(message, &block); end
- def debug(message, &block)
- end
+ def debug(message, &block); end
- def add(severity, message = nil, progname = nil)
- end
+ def trace(message, &block); end
- def <<(message)
- end
+ def add(severity, message = nil, progname = nil); end
+
+ def <<(message); end
def fatal?
false
@@ -68,5 +63,9 @@ class Chef
false
end
+ def trace?
+ false
+ end
+
end
end
diff --git a/lib/chef/org.rb b/lib/chef/org.rb
index a148e37aea..e2b7c49051 100644
--- a/lib/chef/org.rb
+++ b/lib/chef/org.rb
@@ -1,6 +1,6 @@
#
# Author:: Steven Danna (steve@chef.io)
-# Copyright:: Copyright 2014-2016, Chef Software, Inc
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/json_compat"
-require "chef/mixin/params_validate"
-require "chef/server_api"
+require_relative "json_compat"
+require_relative "mixin/params_validate"
+require_relative "server_api"
class Chef
class Org
@@ -40,25 +40,25 @@ class Chef
def name(arg = nil)
set_or_return(:name, arg,
- :regex => /^[a-z0-9\-_]+$/)
+ regex: /^[a-z0-9\-_]+$/)
end
def full_name(arg = nil)
set_or_return(:full_name,
- arg, :kind_of => String)
+ arg, kind_of: String)
end
def private_key(arg = nil)
set_or_return(:private_key,
- arg, :kind_of => String)
+ arg, kind_of: String)
end
def guid(arg = nil)
set_or_return(:guid,
- arg, :kind_of => String)
+ arg, kind_of: String)
end
- def to_hash
+ def to_h
result = {
"name" => @name,
"full_name" => @full_name,
@@ -68,20 +68,22 @@ class Chef
result
end
+ alias_method :to_hash, :to_h
+
def to_json(*a)
- Chef::JSONCompat.to_json(to_hash, *a)
+ Chef::JSONCompat.to_json(to_h, *a)
end
def create
- payload = { :name => self.name, :full_name => self.full_name }
+ payload = { name: name, full_name: full_name }
new_org = chef_rest.post("organizations", payload)
- Chef::Org.from_hash(self.to_hash.merge(new_org))
+ Chef::Org.from_hash(to_h.merge(new_org))
end
def update
- payload = { :name => self.name, :full_name => self.full_name }
+ payload = { name: name, full_name: full_name }
new_org = chef_rest.put("organizations/#{name}", payload)
- Chef::Org.from_hash(self.to_hash.merge(new_org))
+ Chef::Org.from_hash(to_h.merge(new_org))
end
def destroy
@@ -89,22 +91,20 @@ class Chef
end
def save
- begin
- create
- rescue Net::HTTPServerException => e
- if e.response.code == "409"
- update
- else
- raise e
- end
+ create
+ rescue Net::HTTPClientException => e
+ if e.response.code == "409"
+ update
+ else
+ raise e
end
end
def associate_user(username)
- request_body = { :user => username }
+ request_body = { user: username }
response = chef_rest.post "organizations/#{@name}/association_requests", request_body
association_id = response["uri"].split("/").last
- chef_rest.put "users/#{username}/association_requests/#{association_id}", { :response => "accept" }
+ chef_rest.put "users/#{username}/association_requests/#{association_id}", { response: "accept" }
end
def dissociate_user(username)
@@ -124,11 +124,6 @@ class Chef
Chef::Org.from_hash(Chef::JSONCompat.from_json(json))
end
- def self.json_create(json)
- Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::Org#from_json or Chef::Org#load.")
- Chef::Org.from_json(json)
- end
-
def self.load(org_name)
response = Chef::ServerAPI.new(Chef::Config[:chef_server_root]).get("organizations/#{org_name}")
Chef::Org.from_hash(response)
diff --git a/lib/chef/platform.rb b/lib/chef/platform.rb
index 165715267b..db66da221d 100644
--- a/lib/chef/platform.rb
+++ b/lib/chef/platform.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -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_relative "platform/query_helpers"
+require_relative "platform/provider_mapping"
class Chef
class Platform
diff --git a/lib/chef/platform/handler_map.rb b/lib/chef/platform/handler_map.rb
deleted file mode 100644
index da8f84237f..0000000000
--- a/lib/chef/platform/handler_map.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2015-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/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
index c36d00ea5c..45fd1ccd99 100644
--- a/lib/chef/platform/priority_map.rb
+++ b/lib/chef/platform/priority_map.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,13 +16,13 @@
# limitations under the License.
#
-require "chef/node_map"
+require_relative "../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)
+ def priority(resource_name, priority_array, **filter)
+ set_priority_array(resource_name.to_sym, priority_array, **filter)
end
# @api private
@@ -31,9 +31,9 @@ class Chef
end
# @api private
- def set_priority_array(key, priority_array, *filter, &block)
+ def set_priority_array(key, priority_array, **filter, &block)
priority_array = Array(priority_array)
- set(key, priority_array, *filter, &block)
+ set(key, priority_array, **filter, &block)
priority_array
end
end
diff --git a/lib/chef/platform/provider_handler_map.rb b/lib/chef/platform/provider_handler_map.rb
index 26acf150df..12881acddb 100644
--- a/lib/chef/platform/provider_handler_map.rb
+++ b/lib/chef/platform/provider_handler_map.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,13 +16,13 @@
# limitations under the License.
#
-require "singleton"
-require "chef/platform/handler_map"
+require "singleton" unless defined?(Singleton)
+require_relative "../node_map"
class Chef
class Platform
# @api private
- class ProviderHandlerMap < Chef::Platform::HandlerMap
+ class ProviderHandlerMap < Chef::NodeMap
include Singleton
end
end
diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb
index bc565d92ef..a9fba1239b 100644
--- a/lib/chef/platform/provider_mapping.rb
+++ b/lib/chef/platform/provider_mapping.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,54 +16,16 @@
# limitations under the License.
#
-require "chef/log"
-require "chef/exceptions"
-require "chef/mixin/params_validate"
-require "chef/version_constraint/platform"
-require "chef/provider"
+require_relative "../log"
+require_relative "../exceptions"
+require_relative "../mixin/params_validate"
+require_relative "../version_constraint/platform"
+require_relative "../provider"
class Chef
class Platform
class << self
- attr_writer :platforms
-
- def platforms
- @platforms ||= { default: {} }
- end
-
- include Chef::Mixin::ParamsValidate
-
- def find(name, version)
- provider_map = platforms[:default].clone
-
- name_sym = name
- if name.kind_of?(String)
- name = name.downcase
- name.gsub!(/\s/, "_")
- name_sym = name.to_sym
- end
-
- if platforms.has_key?(name_sym)
- platform_versions = platforms[name_sym].select { |k, v| k != :default }
- if platforms[name_sym].has_key?(:default)
- provider_map.merge!(platforms[name_sym][:default])
- end
- platform_versions.each do |platform_version, provider|
- begin
- version_constraint = Chef::VersionConstraint::Platform.new(platform_version)
- if version_constraint.include?(version)
- Chef::Log.debug("Platform #{name} 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
- end
- provider_map
- end
-
def find_platform_and_version(node)
platform = nil
version = nil
@@ -86,132 +48,8 @@ class Chef
raise ArgumentError, "Cannot find a version for #{node}" unless version
- return platform, version
- end
-
- def provider_for_resource(resource, action = :nothing)
- node = resource.run_context && resource.run_context.node
- raise ArgumentError, "Cannot find the provider for a resource with no run context set" unless node
- provider = find_provider_for_node(node, resource).new(resource, resource.run_context)
- provider.action = action
- provider
- end
-
- def provider_for_node(node, resource_type)
- raise NotImplementedError, "#{self.class.name} no longer supports #provider_for_node"
- end
-
- def find_provider_for_node(node, resource_type)
- platform, version = find_platform_and_version(node)
- find_provider(platform, version, resource_type)
- end
-
- def set(args)
- validate(
- args,
- {
- :platform => {
- :kind_of => Symbol,
- :required => false,
- },
- :version => {
- :kind_of => String,
- :required => false,
- },
- :resource => {
- :kind_of => Symbol,
- },
- :provider => {
- :kind_of => [ String, Symbol, Class ],
- },
- }
- )
- if args.has_key?(:platform)
- if args.has_key?(:version)
- if platforms.has_key?(args[:platform])
- if platforms[args[:platform]].has_key?(args[:version])
- platforms[args[:platform]][args[:version]][args[:resource].to_sym] = args[:provider]
- else
- platforms[args[:platform]][args[:version]] = {
- args[:resource].to_sym => args[:provider],
- }
- end
- else
- platforms[args[:platform]] = {
- args[:version] => {
- args[:resource].to_sym => args[:provider],
- },
- }
- end
- else
- if platforms.has_key?(args[:platform])
- if platforms[args[:platform]].has_key?(:default)
- platforms[args[:platform]][:default][args[:resource].to_sym] = args[:provider]
- elsif args[:platform] == :default
- platforms[:default][args[:resource].to_sym] = args[:provider]
- else
- platforms[args[:platform]] = { :default => { args[:resource].to_sym => args[:provider] } }
- end
- else
- platforms[args[:platform]] = {
- :default => {
- args[:resource].to_sym => args[:provider],
- },
- }
- end
- end
- else
- if platforms.has_key?(:default)
- platforms[:default][args[:resource].to_sym] = args[:provider]
- else
- platforms[:default] = {
- args[:resource].to_sym => args[:provider],
- }
- end
- end
- end
-
- def find_provider(platform, version, resource_type)
- provider_klass = explicit_provider(platform, version, resource_type) ||
- platform_provider(platform, version, resource_type) ||
- resource_matching_provider(platform, version, resource_type)
-
- raise Chef::Exceptions::ProviderNotFound, "Cannot find a provider for #{resource_type} on #{platform} version #{version}" if provider_klass.nil?
-
- provider_klass
- end
-
- private
-
- def explicit_provider(platform, version, resource_type)
- resource_type.kind_of?(Chef::Resource) ? resource_type.provider : nil
+ [platform, version]
end
-
- def platform_provider(platform, version, resource_type)
- pmap = Chef::Platform.find(platform, version)
- rtkey = resource_type.kind_of?(Chef::Resource) ? resource_type.resource_name.to_sym : resource_type
- pmap.has_key?(rtkey) ? pmap[rtkey] : nil
- end
-
- include Chef::Mixin::ConvertToClassName
-
- def resource_matching_provider(platform, version, resource_type)
- if resource_type.kind_of?(Chef::Resource)
- class_name = if resource_type.class.name
- resource_type.class.name.split("::").last
- else
- convert_to_class_name(resource_type.resource_name.to_s)
- end
-
- if Chef::Provider.const_defined?(class_name, false)
- 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, false)
- end
- end
- nil
- end
-
end
end
end
diff --git a/lib/chef/platform/provider_priority_map.rb b/lib/chef/platform/provider_priority_map.rb
index 0c8a728618..4507c77833 100644
--- a/lib/chef/platform/provider_priority_map.rb
+++ b/lib/chef/platform/provider_priority_map.rb
@@ -1,5 +1,5 @@
-require "singleton"
-require "chef/platform/priority_map"
+require "singleton" unless defined?(Singleton)
+require_relative "priority_map"
class Chef
class Platform
diff --git a/lib/chef/platform/query_helpers.rb b/lib/chef/platform/query_helpers.rb
index 7d522072a3..bd0703d72a 100644
--- a/lib/chef/platform/query_helpers.rb
+++ b/lib/chef/platform/query_helpers.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,67 +16,31 @@
# limitations under the License.
#
+require "chef-utils" unless defined?(ChefUtils::CANARY)
+
class Chef
class Platform
class << self
def windows?
- 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"
-
- wmi = WmiLite::Wmi.new
- host = wmi.first_of("Win32_OperatingSystem")
- is_server_2003 = (host["version"] && host["version"].start_with?("5.2"))
-
- is_server_2003
+ ChefUtils.windows?
end
+ # @deprecated Windows Nano is not a thing anymore so this check shouldn't be used
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
+ false
end
+ # @deprecated we added this method due to Windows Server Nano, which is no longer a platform
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
+ true
end
+ # @deprecated we don't support any release of Windows that isn't PS 3+
def supports_powershell_execution_bypass?(node)
- node[:languages] && node[:languages][:powershell] &&
- node[:languages][:powershell][:version].to_i >= 3
+ true
end
def supports_dsc?(node)
@@ -94,15 +58,16 @@ class Chef
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"
+ require_relative "../powershell"
+ exec = Chef::PowerShell.new("Get-DscLocalConfigurationManager")
+ exec.error!
+ exec.result["RefreshMode"] == "Disabled"
end
def supported_powershell_version?(node, version_string)
return false unless node[:languages] && node[:languages][:powershell]
- require "rubygems"
+
+ require "rubygems" unless defined?(Gem)
Gem::Version.new(node[:languages][:powershell][:version]) >=
Gem::Version.new(version_string)
end
diff --git a/lib/chef/platform/rebooter.rb b/lib/chef/platform/rebooter.rb
index 74c8b2da1f..c3378e6313 100644
--- a/lib/chef/platform/rebooter.rb
+++ b/lib/chef/platform/rebooter.rb
@@ -16,10 +16,11 @@
# limitations under the License.
#
-require "chef/dsl/reboot_pending"
-require "chef/log"
-require "chef/platform"
-require "chef/application/exit_code"
+require_relative "../dsl/reboot_pending"
+require_relative "../log"
+require_relative "../platform"
+require_relative "../application/exit_code"
+require_relative "../mixin/shell_out"
class Chef
class Platform
@@ -33,12 +34,18 @@ class Chef
def reboot!(node)
reboot_info = node.run_context.reboot_info
- cmd = if Chef::Platform.windows?
+ cmd = case
+ when ChefUtils.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] * 60} /c \"#{reboot_info[:reason]}\""
+ # Use explicit path to shutdown.exe, to protect against https://github.com/chef/chef/issues/5594
+ windows_shutdown_path = "#{ENV["SYSTEMROOT"]}/System32/shutdown.exe"
+ "#{windows_shutdown_path} /r /t #{reboot_info[:delay_mins] * 60} /c \"#{reboot_info[:reason]}\""
+ when node["os"] == "solaris2"
+ # SysV-flavored shutdown
+ "shutdown -i6 -g#{reboot_info[:delay_mins]} -y \"#{reboot_info[:reason]}\" &"
else
- # probably Linux-only.
- "shutdown -r +#{reboot_info[:delay_mins]} \"#{reboot_info[:reason]}\""
+ # Linux/BSD/Mac/AIX and other systems with BSD-ish shutdown
+ "shutdown -r +#{reboot_info[:delay_mins]} \"#{reboot_info[:reason]}\" &"
end
msg = "Rebooting server at a recipe's request. Details: #{reboot_info.inspect}"
@@ -49,8 +56,7 @@ class Chef
raise Chef::Exceptions::RebootFailed.new(e.message)
end
- raise Chef::Exceptions::Reboot.new(msg) if Chef::Application::ExitCode.enforce_rfc_062_exit_codes?
- Chef::Application::ExitCode.notify_reboot_exit_code_deprecation
+ raise Chef::Exceptions::Reboot.new(msg)
end
# this is a wrapper function so Chef::Client only needs a single line of code.
diff --git a/lib/chef/platform/resource_handler_map.rb b/lib/chef/platform/resource_handler_map.rb
index be1b9c28f5..0d54e05069 100644
--- a/lib/chef/platform/resource_handler_map.rb
+++ b/lib/chef/platform/resource_handler_map.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,13 +16,13 @@
# limitations under the License.
#
-require "singleton"
-require "chef/platform/handler_map"
+require "singleton" unless defined?(Singleton)
+require_relative "../node_map"
class Chef
class Platform
# @api private
- class ResourceHandlerMap < Chef::Platform::HandlerMap
+ class ResourceHandlerMap < Chef::NodeMap
include Singleton
end
end
diff --git a/lib/chef/platform/resource_priority_map.rb b/lib/chef/platform/resource_priority_map.rb
index 1871dcd5c6..8995a91f45 100644
--- a/lib/chef/platform/resource_priority_map.rb
+++ b/lib/chef/platform/resource_priority_map.rb
@@ -1,5 +1,5 @@
-require "singleton"
-require "chef/platform/priority_map"
+require "singleton" unless defined?(Singleton)
+require_relative "priority_map"
class Chef
class Platform
diff --git a/lib/chef/platform/service_helpers.rb b/lib/chef/platform/service_helpers.rb
index 87b87d4c72..8f492260b0 100644
--- a/lib/chef/platform/service_helpers.rb
+++ b/lib/chef/platform/service_helpers.rb
@@ -1,6 +1,6 @@
#
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,107 +16,42 @@
# limitations under the License.
#
-require "chef/chef_class"
+require_relative "../chef_class"
+require "chef-utils" unless defined?(ChefUtils::CANARY)
+require_relative "../mixin/chef_utils_wiring" unless defined?(Chef::Mixin::ChefUtilsWiring)
class Chef
class Platform
- class ServiceHelpers
- class << self
- # 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
- # other service providers are narrowly platform-specific with no
- # alternatives.
- #
- # NOTE: if a system has (for example) chkconfig installed then we
- # should report that chkconfig is installed. The fact that a system
- # may also have systemd installed does not mean that we do not
- # report that systemd is also installed. This module is purely for
- # discovery of all the alternatives, handling the priority of the
- # different services is NOT a design concern of this module.
- #
- def service_resource_providers
- providers = []
+ module ServiceHelpers
+ include ChefUtils::DSL::Service
+ include Chef::Mixin::ChefUtilsWiring
- if ::File.exist?(Chef.path_to("/usr/sbin/update-rc.d"))
- providers << :debian
- end
+ def service_resource_providers
+ providers = []
- if ::File.exist?(Chef.path_to("/usr/sbin/invoke-rc.d"))
- providers << :invokercd
- end
+ providers << :debian if debianrcd?
+ providers << :invokercd if invokercd?
+ providers << :upstart if upstart?
+ providers << :insserv if insserv?
+ providers << :systemd if systemd?
+ providers << :redhat if redhatrcd?
- if ::File.exist?(Chef.path_to("/sbin/initctl"))
- providers << :upstart
- end
-
- if ::File.exist?(Chef.path_to("/sbin/insserv"))
- providers << :insserv
- end
-
- if systemd_is_init?
- providers << :systemd
- end
-
- if ::File.exist?(Chef.path_to("/sbin/chkconfig"))
- providers << :redhat
- end
-
- providers
- end
-
- def config_for_service(service_name)
- configs = []
-
- if ::File.exist?(Chef.path_to("/etc/init.d/#{service_name}"))
- configs << :initd
- end
-
- if ::File.exist?(Chef.path_to("/etc/init/#{service_name}.conf"))
- configs << :upstart
- end
-
- if ::File.exist?(Chef.path_to("/etc/xinetd.d/#{service_name}"))
- configs << :xinetd
- end
-
- if ::File.exist?(Chef.path_to("/etc/rc.d/#{service_name}"))
- configs << :etc_rcd
- end
-
- if ::File.exist?(Chef.path_to("/usr/local/etc/rc.d/#{service_name}"))
- configs << :usr_local_etc_rcd
- end
-
- if has_systemd_service_unit?(service_name) || has_systemd_unit?(service_name)
- configs << :systemd
- end
-
- configs
- end
-
- private
+ providers
+ end
- 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 config_for_service(service_name)
+ configs = []
- 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
- end
+ configs << :initd if service_script_exist?(:initd, service_name)
+ configs << :upstart if service_script_exist?(:upstart, service_name)
+ configs << :xinetd if service_script_exist?(:xinetd, service_name)
+ configs << :systemd if service_script_exist?(:systemd, service_name)
+ configs << :etc_rcd if service_script_exist?(:etc_rcd, service_name)
- 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
+ configs
end
+
+ extend self
end
end
end
diff --git a/lib/chef/policy_builder.rb b/lib/chef/policy_builder.rb
index 56533e9a60..0440f6a8d9 100644
--- a/lib/chef/policy_builder.rb
+++ b/lib/chef/policy_builder.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/policy_builder/expand_node_object"
-require "chef/policy_builder/policyfile"
-require "chef/policy_builder/dynamic"
+require_relative "policy_builder/expand_node_object"
+require_relative "policy_builder/policyfile"
+require_relative "policy_builder/dynamic"
class Chef
diff --git a/lib/chef/policy_builder/dynamic.rb b/lib/chef/policy_builder/dynamic.rb
index 5b6bc40f9e..3d9d4c0b7d 100644
--- a/lib/chef/policy_builder/dynamic.rb
+++ b/lib/chef/policy_builder/dynamic.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,13 +16,15 @@
# limitations under the License.
#
-require "forwardable"
+require "forwardable" unless defined?(Forwardable)
-require "chef/log"
-require "chef/run_context"
-require "chef/config"
-require "chef/node"
-require "chef/exceptions"
+require_relative "../log"
+require_relative "../run_context"
+require_relative "../config"
+require_relative "../node"
+require_relative "../exceptions"
+require_relative "expand_node_object"
+require_relative "policyfile"
class Chef
module PolicyBuilder
@@ -63,7 +65,7 @@ class Chef
# @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}")
+ Chef::Log.trace("Building node object for #{node_name}")
@node =
if Chef::Config[:solo_legacy_mode]
@@ -74,6 +76,7 @@ class Chef
select_implementation(node)
implementation.finish_load_node(node)
node
+ events.node_load_success(node)
rescue Exception => e
events.node_load_failed(node_name, e, config)
raise
@@ -173,7 +176,7 @@ class Chef
end
def policyfile_set_in_config?
- config[:use_policyfile] || config[:policy_name] || config[:policy_group]
+ config[:policy_name] || config[:policy_group]
end
def policyfile_compat_mode_config?
diff --git a/lib/chef/policy_builder/expand_node_object.rb b/lib/chef/policy_builder/expand_node_object.rb
index df6956cc77..965e4defe6 100644
--- a/lib/chef/policy_builder/expand_node_object.rb
+++ b/lib/chef/policy_builder/expand_node_object.rb
@@ -3,7 +3,7 @@
# Author:: Tim Hinderliter (<tim@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2008-2016 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,12 +19,12 @@
# limitations under the License.
#
-require "chef/log"
-require "chef/server_api"
-require "chef/run_context"
-require "chef/config"
-require "chef/node"
-require "chef/chef_class"
+require_relative "../log"
+require_relative "../server_api"
+require_relative "../run_context"
+require_relative "../config"
+require_relative "../node"
+require_relative "../chef_class"
class Chef
module PolicyBuilder
@@ -68,66 +68,48 @@ class Chef
Chef.set_run_context(run_context)
end
- def setup_run_context(specific_recipes = nil)
- if Chef::Config[:solo_legacy_mode]
- 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!
- cookbook_collection.install_gems(events)
+ # This not only creates the run_context but this is where we kick off
+ # compiling the entire expanded run_list, loading all the libraries, resources,
+ # attribute files and recipes, and constructing the entire resource collection.
+ # (FIXME: break up creating the run_context and compiling the cookbooks)
+ #
+ def setup_run_context(specific_recipes = nil, run_context = nil)
+ run_context ||= Chef::RunContext.new
+ run_context.events = events
+ run_context.node = node
- 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!
- cookbook_collection.install_gems(events)
+ cookbook_collection =
+ if Chef::Config[:solo_legacy_mode]
+ Chef::Cookbook::FileVendor.fetch_from_disk(Chef::Config[:cookbook_path])
+ cl = Chef::CookbookLoader.new(Chef::Config[:cookbook_path])
+ cl.load_cookbooks
+ Chef::CookbookCollection.new(cl)
+ else
+ Chef::Cookbook::FileVendor.fetch_from_remote(api_service)
+ cookbook_hash = sync_cookbooks
+ Chef::CookbookCollection.new(cookbook_hash)
+ end
- run_context = Chef::RunContext.new(node, cookbook_collection, @events)
- end
+ cookbook_collection.validate!
+ cookbook_collection.install_gems(events)
+
+ run_context.cookbook_collection = cookbook_collection
- # TODO: this is really obviously not the place for this
- # FIXME: need same edits
+ # TODO: move this into the cookbook_compilation_start hook
setup_chef_class(run_context)
- # TODO: this is not the place for this. It should be in Runner or
- # CookbookCompiler or something.
+ events.cookbook_compilation_start(run_context)
+
run_context.load(@run_list_expansion)
if specific_recipes
specific_recipes.each do |recipe_file|
run_context.load_recipe_file(recipe_file)
end
end
- run_context
- end
- # 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
- 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}")
-
- @node =
- if Chef::Config[:solo_legacy_mode]
- Chef::Node.build(node_name)
- else
- Chef::Node.find_or_create(node_name)
- end
- finish_load_node(node)
- node
- rescue Exception => e
- events.node_load_failed(node_name, e, config)
- raise
+ events.cookbook_compilation_complete(run_context)
+
+ run_context
end
def finish_load_node(node)
@@ -157,7 +139,7 @@ class Chef
expand_run_list
Chef::Log.info("Run List is [#{node.run_list}]")
- Chef::Log.info("Run List expands to [#{@expanded_run_list_with_versions.join(', ')}]")
+ 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)
@@ -197,12 +179,12 @@ class Chef
# === Returns
# Hash:: The hash of cookbooks with download URLs as given by the server
def sync_cookbooks
- Chef::Log.debug("Synchronizing cookbooks")
+ Chef::Log.trace("Synchronizing cookbooks")
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)
@@ -232,7 +214,7 @@ class Chef
# 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.
def temporary_policy?
- !node.override_runlist.empty?
+ node.override_runlist_set?
end
########################################
@@ -240,9 +222,9 @@ class Chef
########################################
def setup_run_list_override
- runlist_override_sanity_check!
- unless override_runlist.empty?
- node.override_runlist(*override_runlist)
+ unless override_runlist.nil?
+ runlist_override_sanity_check!
+ node.override_runlist = override_runlist
Chef::Log.warn "Run List override has been provided."
Chef::Log.warn "Original Run List: [#{node.primary_runlist}]"
Chef::Log.warn "Overridden Run List: [#{node.run_list}]"
@@ -253,7 +235,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(&:strip)
end
@override_runlist = [override_runlist].flatten.compact
override_runlist.map! do |item|
@@ -266,7 +248,8 @@ class Chef
end
def api_service
- @api_service ||= Chef::ServerAPI.new(config[:chef_server_url])
+ @api_service ||= Chef::ServerAPI.new(config[:chef_server_url],
+ { version_class: Chef::CookbookManifestVersions })
end
def config
diff --git a/lib/chef/policy_builder/policyfile.rb b/lib/chef/policy_builder/policyfile.rb
index 8d2437fef5..35282bf915 100644
--- a/lib/chef/policy_builder/policyfile.rb
+++ b/lib/chef/policy_builder/policyfile.rb
@@ -3,7 +3,7 @@
# Author:: Tim Hinderliter (<tim@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2008-2016 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,24 +19,19 @@
# limitations under the License.
#
-require "chef/log"
-require "chef/run_context"
-require "chef/config"
-require "chef/node"
-require "chef/server_api"
+require_relative "../log"
+require_relative "../run_context"
+require_relative "../config"
+require_relative "../node"
+require_relative "../server_api"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
module PolicyBuilder
- # Policyfile is an experimental policy builder implementation that gets run
+ # Policyfile is a policy builder implementation that gets run
# list and cookbook version information from a single document.
#
- # == WARNING
- # This implementation is experimental. It may be changed in incompatible
- # ways in minor or even patch releases, or even abandoned altogether. If
- # using this with other tools, you may be forced to upgrade those tools in
- # lockstep with chef-client because of incompatible behavior changes.
- #
# == Unsupported Options:
# * override_runlist:: This could potentially be integrated into the
# policyfile, or replaced with a similar feature that has different
@@ -51,7 +46,34 @@ class Chef
class PolicyfileError < StandardError; end
- RunListExpansionIsh = Struct.new(:recipes, :roles)
+ RunListExpansionIsh = Struct.new(:recipes, :roles) do
+ # Implementing the parts of the RunListExpansion
+ # interface we need to properly send this through to
+ # events.run_list_expanded as it is expecting a RunListExpansion
+ # object.
+ def to_h
+ # It looks like version only gets populated in the expanded_run_list when
+ # using a little used feature of roles to version lock cookbooks, so
+ # version is not reliable in here anyway (places like Automate UI are
+ # not getting version out of here.
+ #
+ # Skipped will always be false as it can only be true when two expanded
+ # roles contain the same recipe.
+ expanded_run_list = recipes.map do |r|
+ { type: "recipe", name: r, skipped: false, version: nil }
+ end
+ data_collector_hash = {}
+ data_collector_hash[:id] = "_policy_node"
+ data_collector_hash[:run_list] = expanded_run_list
+ data_collector_hash
+ end
+
+ alias_method :to_hash, :to_h
+
+ def to_json(*opts)
+ to_h.to_json(*opts)
+ end
+ end
attr_reader :events
attr_reader :node
@@ -69,7 +91,7 @@ class Chef
@node = nil
if Chef::Config[:solo_legacy_mode]
- raise UnsupportedFeature, "Policyfile does not support chef-solo. Use chef-client local mode instead."
+ raise UnsupportedFeature, "Policyfile does not support chef-solo. Use #{ChefUtils::Dist::Infra::CLIENT} local mode instead."
end
if override_runlist
@@ -81,7 +103,7 @@ class Chef
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 an Environment configured."
end
end
@@ -134,9 +156,15 @@ class Chef
apply_policyfile_attributes
Chef::Log.info("Run List is [#{run_list}]")
- Chef::Log.info("Run List expands to [#{run_list_with_versions_for_display.join(', ')}]")
+ Chef::Log.info("Run List expands to [#{run_list_with_versions_for_display.join(", ")}]")
events.node_load_completed(node, run_list_with_versions_for_display, Chef::Config)
+ events.run_list_expanded(run_list_expansion_ish)
+
+ # we must do this after `node.consume_external_attrs`
+ node.automatic_attrs[:policy_name] = node.policy_name
+ node.automatic_attrs[:policy_group] = node.policy_group
+ node.automatic_attrs[:chef_environment] = node.policy_group
node
rescue Exception => e
@@ -148,19 +176,27 @@ class Chef
# run.
#
# @return [Chef::RunContext]
- def setup_run_context(specific_recipes = nil)
- Chef::Cookbook::FileVendor.fetch_from_remote(http_api)
+ def setup_run_context(specific_recipes = nil, run_context = nil)
+ run_context ||= Chef::RunContext.new
+ run_context.node = node
+ run_context.events = events
+
+ Chef::Cookbook::FileVendor.fetch_from_remote(api_service)
sync_cookbooks
cookbook_collection = Chef::CookbookCollection.new(cookbooks_to_sync)
cookbook_collection.validate!
cookbook_collection.install_gems(events)
- run_context = Chef::RunContext.new(node, cookbook_collection, events)
+ run_context.cookbook_collection = cookbook_collection
setup_chef_class(run_context)
+ events.cookbook_compilation_start(run_context)
+
run_context.load(run_list_expansion_ish)
+ events.cookbook_compilation_complete(run_context)
+
setup_chef_class(run_context)
run_context
end
@@ -173,6 +209,7 @@ class Chef
CookbookCacheCleaner.instance.skip_removal = true if named_run_list_requested?
node.run_list(run_list)
+ node.automatic_attrs[:policy_revision] = revision_id
node.automatic_attrs[:roles] = []
node.automatic_attrs[:recipes] = run_list_expansion_ish.recipes
run_list_expansion_ish
@@ -184,7 +221,7 @@ class Chef
# @return [Hash{String => Chef::CookbookManifest}] A map of
# CookbookManifest objects by cookbook name.
def sync_cookbooks
- Chef::Log.debug("Synchronizing cookbooks")
+ Chef::Log.trace("Synchronizing cookbooks")
synchronizer = Chef::CookbookSynchronizer.new(cookbooks_to_sync, events)
synchronizer.sync_cookbooks
@@ -236,6 +273,16 @@ class Chef
def apply_policyfile_attributes
node.attributes.role_default = policy["default_attributes"]
node.attributes.role_override = policy["override_attributes"]
+ hoist_policyfile_attributes(node.policy_group) if node.policy_group
+ end
+
+ # @api private
+ #
+ # Hoists attributes from role_X[policy_group] up to the equivalent role_X level
+ def hoist_policyfile_attributes(policy_group)
+ Chef::Log.trace("Running attribute Hoist for group #{policy_group}")
+ Chef::Mixin::DeepMerge.hash_only_merge!(node.role_default, node.role_default[policy_group]) if node.role_default.include?(policy_group)
+ Chef::Mixin::DeepMerge.hash_only_merge!(node.role_override, node.role_override[policy_group]) if node.role_override.include?(policy_group)
end
# @api private
@@ -258,7 +305,7 @@ class Chef
if named_run_list_requested?
named_run_list || 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(', ')}])")
+ "(available named_run_lists: [#{available_named_run_lists.join(", ")}])")
else
policy["run_list"]
end
@@ -266,8 +313,8 @@ class Chef
# @api private
def policy
- @policy ||= http_api.get(policyfile_location)
- rescue Net::HTTPServerException => e
+ @policy ||= api_service.get(policyfile_location)
+ rescue Net::HTTPClientException => e
raise ConfigurationError, "Error loading policyfile from `#{policyfile_location}': #{e.class} - #{e.message}"
end
@@ -281,7 +328,7 @@ class Chef
end
end
- # Do some mimimal validation of the policyfile we fetched from the
+ # Do some minimal validation of the policyfile we fetched from the
# server. Compatibility mode relies on using data bags to store policy
# files; therefore no real validation will be performed server-side and
# we need to make additional checks to ensure the data will be formatted
@@ -294,7 +341,7 @@ class Chef
unless policy.key?("cookbook_locks")
errors << "Policyfile is missing cookbook_locks element"
end
- if run_list.kind_of?(Array)
+ if run_list.is_a?(Array)
run_list_errors = run_list.select do |maybe_recipe_spec|
validate_recipe_spec(maybe_recipe_spec)
end
@@ -363,6 +410,7 @@ class Chef
node.policy_name = policy_name_to_set
node.policy_group = policy_group_to_set
+ node.chef_environment = policy_group_to_set
Chef::Config[:policy_name] = policy_name_to_set
Chef::Config[:policy_group] = policy_group_to_set
@@ -427,7 +475,7 @@ class Chef
end
# @api private
- # Fetches the CookbookVersion object for the given name and identifer
+ # Fetches the CookbookVersion object for the given name and identifier
# specified in the lock_data.
# TODO: This only implements Chef 11 compatibility mode, which means that
# cookbooks are fetched by the "dotted_decimal_identifier": a
@@ -451,8 +499,9 @@ class Chef
end
# @api private
- def http_api
- @api_service ||= Chef::ServerAPI.new(config[:chef_server_url])
+ def api_service
+ @api_service ||= Chef::ServerAPI.new(config[:chef_server_url],
+ { version_class: Chef::CookbookManifestVersions })
end
# @api private
@@ -495,7 +544,7 @@ class Chef
def compat_mode_manifest_for(cookbook_name, lock_data)
xyz_version = lock_data["dotted_decimal_identifier"]
rel_url = "cookbooks/#{cookbook_name}/#{xyz_version}"
- inflate_cbv_object(http_api.get(rel_url))
+ inflate_cbv_object(api_service.get(rel_url))
rescue Exception => e
message = "Error loading cookbook #{cookbook_name} at version #{xyz_version} from #{rel_url}: #{e.class} - #{e.message}"
err = Chef::Exceptions::CookbookNotFound.new(message)
@@ -506,7 +555,7 @@ class Chef
def artifact_manifest_for(cookbook_name, lock_data)
identifier = lock_data["identifier"]
rel_url = "cookbook_artifacts/#{cookbook_name}/#{identifier}"
- inflate_cbv_object(http_api.get(rel_url))
+ inflate_cbv_object(api_service.get(rel_url))
rescue Exception => e
message = "Error loading cookbook #{cookbook_name} with identifier #{identifier} from #{rel_url}: #{e.class} - #{e.message}"
err = Chef::Exceptions::CookbookNotFound.new(message)
diff --git a/lib/chef/powershell.rb b/lib/chef/powershell.rb
new file mode 100644
index 0000000000..b49d3c58e4
--- /dev/null
+++ b/lib/chef/powershell.rb
@@ -0,0 +1,79 @@
+#
+# Author:: Stuart Preston (<stuart@chef.io>)
+# Copyright:: Copyright (c) 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 "ffi" unless defined?(FFI)
+require_relative "json_compat"
+
+class Chef
+ class PowerShell
+ extend FFI::Library
+
+ attr_reader :result
+ attr_reader :errors
+ attr_reader :verbose
+
+ # Run a command under PowerShell via FFI
+ # This implementation requires the managed dll and native wrapper to be in the library search
+ # path on Windows (i.e. c:\windows\system32 or in the same location as ruby.exe).
+ #
+ # Requires: .NET Framework 4.0 or higher on the target machine.
+ #
+ # @param script [String] script to run
+ # @return [Object] output
+ def initialize(script)
+ # This Powershell DLL source lives here: https://github.com/chef/chef-powershell-shim
+ # Every merge into that repo triggers a Habitat build and promotion. Running
+ # the rake :update_chef_exec_dll task in this (chef/chef) repo will pull down
+ # the built packages and copy the binaries to distro/ruby_bin_folder. Bundle install
+ # ensures that the correct architecture binaries are installed into the path.
+ @dll ||= "Chef.PowerShell.Wrapper.dll"
+ exec(script)
+ end
+
+ #
+ # Was there an error running the command
+ #
+ # @return [Boolean]
+ #
+ def error?
+ return true if errors.count > 0
+
+ false
+ end
+
+ class CommandFailed < RuntimeError; end
+
+ #
+ # @raise [Chef::PowerShell::CommandFailed] raise if the command failed
+ #
+ def error!
+ raise Chef::PowerShell::CommandFailed, "Unexpected exit in PowerShell command: #{@errors}" if error?
+ end
+
+ protected
+
+ def exec(script)
+ FFI.ffi_lib @dll
+ FFI.attach_function :execute_powershell, :ExecuteScript, [:string], :pointer
+ execution = FFI.execute_powershell(script).read_utf16string
+ hashed_outcome = Chef::JSONCompat.parse(execution)
+ @result = Chef::JSONCompat.parse(hashed_outcome["result"])
+ @errors = hashed_outcome["errors"]
+ @verbose = hashed_outcome["verbose"]
+ end
+ end
+end
diff --git a/lib/chef/property.rb b/lib/chef/property.rb
index a357ba9ee3..bbf06854d6 100644
--- a/lib/chef/property.rb
+++ b/lib/chef/property.rb
@@ -1,6 +1,7 @@
#
# Author:: John Keiser <jkeiser@chef.io>
-# Copyright:: Copyright 2015-2016, John Keiser.
+# Copyright:: Copyright 2015-2016, John Keiser
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,10 +17,10 @@
# limitations under the License.
#
-require "chef/exceptions"
-require "chef/delayed_evaluator"
-require "chef/chef_class"
-require "chef/log"
+require_relative "exceptions"
+require_relative "delayed_evaluator"
+require_relative "chef_class"
+require_relative "log"
class Chef
#
@@ -51,6 +52,27 @@ class Chef
new(**options)
end
+ # This is to support #deprecated_property_alias, by emitting an alias and a
+ # deprecation warning when called.
+ #
+ # @param from [String] Name of the deprecated property
+ # @param to [String] Name of the correct property
+ # @param message [String] Deprecation message to show to the cookbook author
+ # @param declared_in [Class] Class this property comes from
+ #
+ def self.emit_deprecated_alias(from, to, message, declared_in)
+ declared_in.class_eval <<-EOM, __FILE__, __LINE__ + 1
+ def #{from}(value=NOT_PASSED)
+ Chef.deprecated(:property, "#{message}")
+ #{to}(value)
+ end
+ def #{from}=(value)
+ Chef.deprecated(:property, "#{message}")
+ #{to} = value
+ end
+ EOM
+ end
+
#
# Create a new property.
#
@@ -60,10 +82,12 @@ class Chef
# options).
# @option options [Symbol] :name The name of this property.
# @option options [Class] :declared_in The class this property comes from.
+ # @option options [String] :description A description of the property.
# @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 [String] :introduced The release that introduced this property
# @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
@@ -74,12 +98,16 @@ class Chef
# return `true` if the property is set *or* if `name` is set.
# @option options [Boolean] :nillable `true` opt-in to Chef-13 style behavior where
# attempting to set a nil value will really set a nil value instead of issuing
- # a warning and operating like a getter
+ # a warning and operating like a getter [DEPRECATED]
# @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 [String] :default_description The description of the default value
+ # used in docs. Particularly useful when a default is computed or lazily eval'd.
+ # @option options [Boolean] :skip_docs This property should not be included in any
+ # documentation output
# @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*
@@ -88,6 +116,8 @@ class Chef
# @option options [Boolean] :required `true` if this property
# must be present; `false` otherwise. This is checked after the resource
# is fully initialized.
+ # @option options [String] :deprecated If set, this property is deprecated and
+ # will create a deprecation warning.
#
def initialize(**options)
options = options.inject({}) { |memo, (key, value)| memo[key.to_sym] = value; memo }
@@ -96,27 +126,37 @@ class Chef
options[:instance_variable_name] = options[:instance_variable_name].to_sym if options[:instance_variable_name]
# Replace name_attribute with name_property
- if options.has_key?(:name_attribute)
+ if options.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 #{self}."
+ if options.key?(:name_property)
+ raise ArgumentError, "name_attribute and name_property are functionally identical and both cannot be specified on a property at once. Use just one on property #{self}"
end
+
# replace name_property with name_attribute in place
options = Hash[options.map { |k, v| k == :name_attribute ? [ :name_property, v ] : [ k, v ] }]
@options = options
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
+ if options.key?(:default) && options.key?(:name_property)
+ raise ArgumentError, "A property cannot be both a name_property/name_attribute and have a default value. Use one or the other on property #{self}"
+ end
+
+ if options[:name_property]
+ options[:desired_state] = false unless options.key?(:desired_state)
+ end
+
+ # Recursively freeze the default if it isn't a lazy value.
+ unless default.is_a?(DelayedEvaluator)
+ visitor = lambda do |obj|
+ case obj
+ when Hash
+ obj.each_value { |value| visitor.call(value) }
+ when Array
+ obj.each { |value| visitor.call(value) }
+ end
+ obj.freeze
end
- Chef.log_deprecation("Cannot specify both default and name_property together on property #{self}. Only one (#{preferred_default}) will be obeyed. In Chef 13, this will become an error. Please remove one or the other from the property.")
+ visitor.call(default)
end
# Validate the default early, so the user gets a good error message, and
@@ -153,6 +193,24 @@ class Chef
end
#
+ # A description of this property.
+ #
+ # @return [String]
+ #
+ def description
+ options[:description]
+ end
+
+ #
+ # When this property was introduced
+ #
+ # @return [String]
+ #
+ def introduced
+ options[:introduced]
+ end
+
+ #
# The instance variable associated with this property.
#
# Defaults to `@<name>`
@@ -160,7 +218,7 @@ class Chef
# @return [Symbol]
#
def instance_variable_name
- if options.has_key?(:instance_variable_name)
+ if options.key?(:instance_variable_name)
options[:instance_variable_name]
elsif name
:"@#{name}"
@@ -176,12 +234,31 @@ class Chef
# `nil`
#
def default
- return options[:default] if options.has_key?(:default)
+ return options[:default] if options.key?(:default)
return Chef::DelayedEvaluator.new { name } if name_property?
+
nil
end
#
+ # A description of the default value of this property.
+ #
+ # @return [String]
+ #
+ def default_description
+ options[:default_description]
+ end
+
+ #
+ # The equal_to field of this property.
+ #
+ # @return [Array, NilClass]
+ #
+ def equal_to
+ options[:equal_to]
+ end
+
+ #
# Whether this is part of the resource's natural identity or not.
#
# @return [Boolean]
@@ -198,7 +275,8 @@ class Chef
# @return [Boolean]
#
def desired_state?
- return true if !options.has_key?(:desired_state)
+ return true unless options.key?(:desired_state)
+
options[:desired_state]
end
@@ -217,7 +295,7 @@ class Chef
# @return [Boolean]
#
def has_default?
- options.has_key?(:default) || name_property?
+ options.key?(:default) || name_property?
end
#
@@ -225,8 +303,23 @@ class Chef
#
# @return [Boolean]
#
- def required?
- options[:required]
+ def required?(action = nil)
+ if !action.nil? && options[:required].is_a?(Array)
+ options[:required].include?(action)
+ else
+ !!options[:required]
+ end
+ end
+
+ #
+ # Whether this property should be skipped for documentation purposes.
+ #
+ # Defaults to false.
+ #
+ # @return [Boolean]
+ #
+ def skip_docs?
+ options.fetch(:skip_docs, false)
end
#
@@ -247,7 +340,7 @@ class Chef
#
def validation_options
@validation_options ||= options.reject do |k, v|
- [:declared_in, :name, :instance_variable_name, :desired_state, :identity, :default, :name_property, :coerce, :required, :nillable, :sensitive].include?(k)
+ %i{declared_in name instance_variable_name desired_state identity default name_property coerce required nillable sensitive description introduced deprecated default_description skip_docs}.include?(k)
end
end
@@ -272,33 +365,9 @@ class Chef
# `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? && !nillable?
- # 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, nil_set: true)
-
- # Warn about this becoming a set in Chef 13.
- begin
- input_to_stored_value(resource, value)
- # If nil is valid, and it would change the value, warn that this will change to a set.
- if !result.nil?
- Chef.log_deprecation("An attempt was made to change #{name} from #{result.inspect} to nil by calling #{name}(nil). In Chef 12, this does a get rather than a set. In Chef 13, this will change to set the value to nil.")
- end
- rescue Chef::Exceptions::DeprecatedFeatureError
- raise
- rescue
- # If nil is invalid, warn that this will become an error.
- Chef.log_deprecation("nil is an invalid value for #{self}. In Chef 13, this warning will change to an error. Error: #{$!}")
- end
-
- result
+ if NOT_PASSED == value # see https://github.com/chef/chef/pull/8781 before changing this
+ get(resource)
else
- # Anything else, such as myprop(value) is a set
set(resource, value)
end
end
@@ -327,38 +396,14 @@ class Chef
#
def get(resource, nil_set: false)
# If it's set, return it (and evaluate any lazy values)
+ value = nil
+
if is_set?(resource)
value = get_value(resource)
value = stored_value_to_output(resource, value)
-
else
# We are getting the default value.
- # 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 !nil_set &&
- resource.respond_to?(:resource_initializing) &&
- resource.resource_initializing &&
- resource.respond_to?(:enclosing_provider) &&
- resource.enclosing_provider &&
- resource.enclosing_provider.new_resource &&
- resource.enclosing_provider.new_resource.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?
# If we were able to cache the stored_default, grab it.
if defined?(@stored_default)
@@ -367,7 +412,7 @@ class Chef
# Otherwise, we have to validate it now.
value = input_to_stored_value(resource, default, is_default: true)
end
- value = stored_value_to_output(resource, value, is_default: true)
+ value = stored_value_to_output(resource, value)
# If the value is mutable (non-frozen), we set it on the instance
# so that people can mutate it. (All constant default values are
@@ -375,13 +420,14 @@ class Chef
if !value.frozen? && !value.nil?
set_value(resource, value)
end
-
- value
-
- elsif required?
- raise Chef::Exceptions::ValidationFailed, "#{name} is required"
end
end
+
+ if value.nil? && required?
+ raise Chef::Exceptions::ValidationFailed, "#{name} is a required property"
+ else
+ value
+ end
end
#
@@ -400,7 +446,17 @@ class Chef
# this property.
#
def set(resource, value)
- set_value(resource, input_to_stored_value(resource, value))
+ value = set_value(resource, input_to_stored_value(resource, value))
+
+ if options.key?(:deprecated)
+ Chef.deprecated(:property, options[:deprecated])
+ end
+
+ if value.nil? && required?
+ raise Chef::Exceptions::ValidationFailed, "#{name} is a required property"
+ else
+ value
+ end
end
#
@@ -452,9 +508,9 @@ class Chef
# this property.
#
def coerce(resource, value)
- if options.has_key?(:coerce)
- # If we have no default value, `nil` is never coerced or validated
- unless !has_default? && value.nil?
+ if options.key?(:coerce)
+ # nil is never coerced
+ unless value.nil?
value = exec_in_resource(resource, options[:coerce], value)
end
end
@@ -468,15 +524,15 @@ class Chef
# options.
#
# @param resource [Chef::Resource] The resource we're validating against
- # (to provide context for the validate).
+ # (to provide context for the validation).
# @param value The value to validate.
#
# @raise Chef::Exceptions::ValidationFailed If the value is invalid for
# this property.
#
def validate(resource, value)
- # If we have no default value, `nil` is never coerced or validated
- unless value.nil? && !has_default?
+ # nils are not validated unless we have an explicit default value
+ if !value.nil? || has_default?
if resource
resource.validate({ name => value }, { name => validation_options })
else
@@ -500,12 +556,12 @@ class Chef
# 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 }
+ if modified_options.key?(:name_property) ||
+ modified_options.key?(:name_attribute) ||
+ modified_options.key?(:default)
+ options = options.reject { |k, v| %i{name_attribute name_property default}.include?(k) }
end
- self.class.new(options.merge(modified_options))
+ self.class.new(**options.merge(modified_options))
end
#
@@ -516,30 +572,30 @@ class Chef
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
+ return unless instance_variable_name
+
+ # Properties may override existing properties up the inheritance hierarchy, but
+ # properties must not override inherited methods like Object#hash. When the Resource is
+ # placed into the resource collection the ruby Hash object will call the
+ # Object#hash method on the resource, and overriding that with a property will cause
+ # very confusing results.
+ if property_redefines_method?
+ resource_name = declared_in.respond_to?(:resource_name) ? declared_in.resource_name : declared_in
+ raise ArgumentError, "Property `#{name}` of resource `#{resource_name}` overwrites an existing method. A different name should be used for this property."
+ end
# 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?
+ raise "Property `#{name}` of `\#{self}` was incorrectly passed a block. Possible property-resource collision. To call a resource named `#{name}` either rename the property or else use `declare_resource(:#{name}, ...)`" 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?
+ raise "Property `#{name}` of `\#{self}` was incorrectly passed a block. Possible property-resource collision. To call a resource named `#{name}` either rename the property or else use `declare_resource(:#{name}, ...)`" 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
#
@@ -588,8 +644,10 @@ class Chef
#
# @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))
+ options.key?(:coerce) ||
+ (options.key?(:is) && Chef::Mixin::ParamsValidate.send(:_pv_is, { name => nil }, name, options[:is]))
+ rescue Chef::Exceptions::ValidationFailed, Chef::Exceptions::CannotValidateStaticallyError
+ false
end
# @api private
@@ -632,6 +690,25 @@ class Chef
private
+ def property_redefines_method?
+ # We only emit deprecations if this property already exists as an instance method.
+ # Weeding out class methods avoids unnecessary deprecations such Chef::Resource
+ # defining a `name` property when there's an already-existing `name` method
+ # for a Module.
+ return false unless declared_in.instance_methods.include?(name)
+
+ # Only emit deprecations for some well-known classes. This will still
+ # allow more advanced users to subclass their own custom resources and
+ # override their own properties.
+ return false unless [ Object, BasicObject, Kernel, Chef::Resource ].include?(declared_in.instance_method(name).owner)
+
+ # Allow top-level Chef::Resource properties, such as `name`, to be overridden.
+ # As of this writing, `name` is the only Chef::Resource property created with the
+ # `property` definition, but this will allow for future properties to be extended
+ # as needed.
+ !Chef::Resource.properties.key?(name)
+ end
+
def exec_in_resource(resource, proc, *args)
if resource
if proc.arity > args.size
@@ -646,51 +723,30 @@ class Chef
end
def input_to_stored_value(resource, value, is_default: false)
+ if value.nil? && !is_default && !explicitly_accepts_nil?(resource)
+ value = default
+ end
unless value.is_a?(DelayedEvaluator)
- value = coerce_and_validate(resource, value, is_default: is_default)
+ value = coerce_and_validate(resource, value)
end
value
end
- def stored_value_to_output(resource, value, is_default: false)
+ def stored_value_to_output(resource, value)
# Crack open lazy values before giving the result to the user
if value.is_a?(DelayedEvaluator)
value = exec_in_resource(resource, value)
- value = coerce_and_validate(resource, value, is_default: is_default)
+ value = coerce_and_validate(resource, value)
end
value
end
- # Coerces and validates the value. If the value is a default, it will warn
- # the user that invalid defaults are bad mmkay, and return it as if it were
- # valid.
- def coerce_and_validate(resource, value, is_default: false)
+ # Coerces and validates the value.
+ def coerce_and_validate(resource, value)
result = coerce(resource, value)
- begin
- # If the input is from a default, we need to emit an invalid default warning on validate.
- validate(resource, result)
- rescue Chef::Exceptions::CannotValidateStaticallyError
- # This one gets re-raised
- raise
- rescue
- # Anything else is just an invalid default: in those cases, we just
- # warn and return the (possibly coerced) value to the user.
- if is_default
- if value.nil?
- Chef.log_deprecation("Default value nil is invalid for property #{self}. Possible fixes: 1. Remove 'default: nil' if nil means 'undefined'. 2. Set a valid default value if there is a reasonable one. 3. Allow nil as a valid value of your property (for example, 'property #{name.inspect}, [ String, nil ], default: nil'). Error: #{$!}")
- else
- Chef.log_deprecation("Default value #{value.inspect} is invalid for property #{self}. In Chef 13 this will become an error: #{$!}.")
- end
- else
- raise
- end
- end
+ validate(resource, result)
result
end
-
- def nillable?
- !!options[:nillable]
- end
end
end
diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb
index 7cfddba0cb..81ed530fc7 100644
--- a/lib/chef/provider.rb
+++ b/lib/chef/provider.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
-# Copyright:: Copyright 2008-2016, 2009-2016 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,65 +17,123 @@
# 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/provides"
-require "chef/dsl/core"
-require "chef/platform/service_helpers"
-require "chef/node_map"
-require "forwardable"
+require_relative "mixin/from_file"
+require_relative "mixin/convert_to_class_name"
+require_relative "mixin/enforce_ownership_and_permissions"
+require_relative "mixin/why_run"
+require_relative "mixin/shell_out"
+require_relative "mixin/provides"
+require_relative "dsl/recipe"
+require_relative "platform/service_helpers"
+require_relative "node_map"
+require "forwardable" unless defined?(Forwardable)
class Chef
class Provider
- require "chef/mixin/why_run"
- require "chef/mixin/provides"
-
attr_accessor :new_resource
attr_accessor :current_resource
+ attr_accessor :after_resource
attr_accessor :run_context
attr_reader :recipe_name
- attr_reader :cookbook_name
+ attr_reader :logger
include Chef::Mixin::WhyRun
extend Chef::Mixin::Provides
+ extend Forwardable
- # includes the "core" DSL and not the "recipe" DSL by design
- include Chef::DSL::Core
+ include Chef::DSL::Recipe
+ # the class only gets the Universal DSL (no resource_collection at class parsing time)
+ extend Chef::DSL::Universal
# supports the given resource and action (late binding)
def self.supports?(resource, action)
true
end
- #--
- # TODO: this should be a reader, and the action should be passed in the
+ # 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)
+ #
+ # @since 13.0
+ # @param name [String, Symbol] Name of the action to define.
+ # @param block [Proc] Body of the action.
+ #
+ # @return [void]
+ def self.action(name, &block)
+ # We need the block directly in a method so that `return` works.
+ define_method("compile_action_#{name}", &block)
+ class_eval <<-EOM
+ def action_#{name}
+ compile_and_converge_action { compile_action_#{name} }
+ end
+ EOM
+ end
+
+ # Deprecation stub for the old use_inline_resources mode.
+ #
+ # @return [void]
+ def self.use_inline_resources
+ # Uncomment this in Chef 13.6.
+ # Chef.deprecated(:use_inline_resources, "The use_inline_resources mode is no longer optional and the line enabling it can be removed")
+ end
+
+ # Use a partial code fragment. This can be used for code sharing between multiple resources.
+ #
+ # Do not wrap the code fragment in a class or module. It also does not support the use of super
+ # to subclass any methods defined in the fragment, the methods will just be overwritten.
+ #
+ # @param partial [String] the code fragment to eval against the class
+ #
+ def self.use(partial)
+ dirname = ::File.dirname(partial)
+ basename = ::File.basename(partial, ".rb")
+ basename = basename[1..] if basename.start_with?("_")
+ class_eval IO.read(::File.expand_path("#{dirname}/_#{basename}.rb", ::File.dirname(caller_locations.first.absolute_path)))
+ end
+
+ # delegate to the resource
+ #
+ def_delegators :@new_resource, :property_is_set?
+
+ # @todo this should be a reader, and the action should be passed in the
# constructor; however, many/most subclasses override the constructor so
# changing the arity would be a breaking change. Change this at the next
- # break, e.g., Chef 11.
+ # major release
attr_accessor :action
def initialize(new_resource, run_context)
@new_resource = new_resource
@action = action
@current_resource = nil
+ @after_resource = nil
@run_context = run_context
@converge_actions = nil
+ @logger = if run_context
+ run_context.logger.with_child({ resource: new_resource.name, cookbook: cookbook_name, recipe: recipe_name })
+ else
+ Chef::Log.with_child({ resource: new_resource.name, cookbook: cookbook_name, recipe: recipe_name })
+ end
+
@recipe_name = nil
@cookbook_name = nil
self.class.include_resource_dsl_module(new_resource)
end
+ # has why-run mode been enabled?
+ #
+ # @return [Boolean]
def whyrun_mode?
Chef::Config[:why_run]
end
+ # as of Chef 13 we enable why-run by default and require resources to override this to set false.
+ # We're keeping this method to prevent breaking the cookbook world for no real gain on our part.
+ #
+ # @return [Boolean]
def whyrun_supported?
- false
+ true
end
def node
@@ -91,21 +149,29 @@ class Chef
new_resource.cookbook_name
end
- def check_resource_semantics!
- end
+ # hook that subclasses can use to do lazy validation for where properties aren't flexible enough
+ def check_resource_semantics!; end
+ # a simple placeholder method that will be called / raise if a resource tries to
+ # use current_resource without defining a load_current_resource method.
def load_current_resource
raise Chef::Exceptions::Override, "You must override load_current_resource in #{self}"
end
- def define_resource_requirements
- end
+ def define_resource_requirements; end
- def cleanup_after_converge
+ def cleanup_after_converge; end
+
+ def load_after_resource
+ # This is a backwards compatible hack, custom resources properly wire up a new after_resource
+ # via load_current_value. It is acceptable for old style resources that cannot be easily made
+ # into custom resources to override this method and provide a proper after_resource.
+ @after_resource = @new_resource
end
+ # the :nothing action which is available on all resources by default
def action_nothing
- Chef::Log.debug("Doing nothing for #{@new_resource}")
+ logger.trace("Doing nothing for #{@new_resource}")
true
end
@@ -113,14 +179,20 @@ class Chef
run_context.events
end
+ def validate_required_properties!
+ # all we do is run through all the required properties for this action and vivify them
+ new_resource.class.properties.each { |name, property| property.required?(action) && property.get(new_resource) }
+ end
+
def run_action(action = nil)
@action = action unless action.nil?
- # TODO: it would be preferable to get the action to be executed in the
- # constructor...
-
+ # hook that subclasses can use to do lazy validation for where properties aren't flexible enough
check_resource_semantics!
+ # force the validation of required properties
+ validate_required_properties!
+
# user-defined LWRPs may include unsafe load_current_resource methods that cannot be run in whyrun mode
if whyrun_mode? && !whyrun_supported?
events.resource_current_state_load_bypassed(@new_resource, @action, @current_resource)
@@ -148,6 +220,9 @@ class Chef
set_updated_status
cleanup_after_converge
+
+ load_after_resource
+ events.resource_after_state_loaded(@new_resource, @action, @after_resource)
end
def process_resource_requirements
@@ -172,10 +247,42 @@ class Chef
@requirements ||= ResourceRequirements.new(@new_resource, run_context)
end
+ def description(description = "NOT_PASSED")
+ if description != "NOT_PASSED"
+ @description = description
+ end
+ @description
+ end
+
+ def introduced(introduced = "NOT_PASSED")
+ if introduced != "NOT_PASSED"
+ @introduced = introduced
+ end
+ @introduced
+ end
+
def converge_by(descriptions, &block)
converge_actions.add_action(descriptions, &block)
end
+ # 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
+ @run_context.resource_collection.unified_mode = new_resource.class.unified_mode
+ runner = Chef::Runner.new(@run_context)
+ return_value = instance_eval(&block)
+ runner.converge
+ return_value
+ ensure
+ if run_context.resource_collection.any?(&:updated?)
+ new_resource.updated_by_last_action(true)
+ end
+ @run_context = old_run_context
+ end
+
#
# Handle patchy convergence safely.
#
@@ -194,30 +301,42 @@ class Chef
# @return [Boolean] whether the block was executed.
#
def converge_if_changed(*properties, &converge_block)
- if !converge_block
+ unless 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 }
+ properties =
+ if properties.empty?
+ new_resource.class.state_properties
+ else
+ properties.map { |property| new_resource.class.properties[property] }
+ end
+
if current_resource
# Collect the list of modified properties
- specified_properties = properties.select { |property| new_resource.property_is_set?(property) }
+ specified_properties = properties.select { |property| property.is_set?(new_resource) || property.has_default? }
+ specified_properties = specified_properties.map(&:name).map(&:to_sym)
modified = specified_properties.select { |p| new_resource.send(p) != current_resource.send(p) }
if modified.empty?
- properties_str = if sensitive
+ properties_str = if new_resource.sensitive
specified_properties.join(", ")
else
- specified_properties.map { |p| "#{p}=#{new_resource.send(p).inspect}" }.join(", ")
+ specified_properties.map do |property|
+ "#{property}=" << if new_resource.class.properties[property].sensitive?
+ "(suppressed sensitive property)"
+ else
+ new_resource.send(property).inspect
+ end
+ end.join(", ")
end
- Chef::Log.debug("Skipping update of #{new_resource}: has not changed any of the specified properties #{properties_str}.")
+ logger.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
+ property_size = modified.map(&:size).max
modified.map! do |p|
- properties_str = if sensitive
+ properties_str = if new_resource.sensitive || new_resource.class.properties[p].sensitive?
"(suppressed sensitive property)"
else
"#{new_resource.send(p).inspect} (was #{current_resource.send(p).inspect})"
@@ -229,15 +348,15 @@ class Chef
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
+ property_size = properties.map(&:name).map(&:to_sym).map(&:size).max
created = properties.map do |property|
- default = " (default value)" unless new_resource.property_is_set?(property)
- properties_str = if sensitive
+ default = " (default value)" unless property.is_set?(new_resource)
+ properties_str = if new_resource.sensitive || property.sensitive?
"(suppressed sensitive property)"
else
- new_resource.send(property).inspect
+ new_resource.send(property.name.to_sym).inspect
end
- " set #{property.to_s.ljust(property_size)} to #{properties_str}#{default}"
+ " set #{property.name.to_sym.to_s.ljust(property_size)} to #{properties_str}#{default}"
end
converge_by([ "create #{new_resource.identity}" ] + created, &converge_block)
@@ -246,7 +365,7 @@ class Chef
end
def self.provides(short_name, opts = {}, &block)
- Chef.provider_handler_map.set(short_name, self, opts, &block)
+ Chef.provider_handler_map.set(short_name, self, **opts, &block)
end
def self.provides?(node, resource)
@@ -267,134 +386,41 @@ class Chef
# @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
+ def self.include_resource_dsl?
+ false
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)
+ 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
+ # this magic, stated simply, is that any instance method declared directly on
+ # the resource we are building, will be accessible from the action_class(provider)
+ # instance. methods declared on Chef::Resource and properties are not inherited.
dsl_methods =
resource.class.public_instance_methods +
resource.class.protected_instance_methods -
provider_class.instance_methods -
- resource.class.properties.keys
+ resource.class.properties.keys -
+ resource.class.properties.keys.map { |k| "#{k}=".to_sym } -
+ Chef::Resource.instance_methods
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
- end
-
protected
+ # stores all actions that have been converged
+ #
+ # @return [ConvergeActions]
def converge_actions
@converge_actions ||= ConvergeActions.new(@new_resource, run_context, @action)
end
@@ -409,7 +435,7 @@ class Chef
# this block cannot interact with resources outside, e.g.,
# manipulating notifies.
- converge_by ("evaluate block and run any associated actions") do
+ converge_by("evaluate block and run any associated actions") do
saved_run_context = run_context
begin
@run_context = run_context.create_child
@@ -421,38 +447,10 @@ class Chef
end
end
- module DeprecatedLWRPClass
- def const_missing(class_name)
- if Chef::Provider.deprecated_constants[class_name.to_sym]
- Chef.log_deprecation("Using an LWRP provider by its name (#{class_name}) directly is no longer supported in Chef 12 and will be removed. Use Chef::ProviderResolver.new(node, resource, action) instead.")
- Chef::Provider.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
- Chef::Provider.deprecated_constants[class_name.to_sym] = provider_class
- end
- end
-
- def deprecated_constants
- raise "Deprecated constants should be called only on Chef::Provider" unless self == Chef::Provider
- @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"
+require_relative "chef_class"
+require_relative "resource_collection"
+require_relative "runner"
diff --git a/lib/chef/provider/apt_repository.rb b/lib/chef/provider/apt_repository.rb
deleted file mode 100644
index 9e077c8cbb..0000000000
--- a/lib/chef/provider/apt_repository.rb
+++ /dev/null
@@ -1,253 +0,0 @@
-#
-# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright (c) 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/resource"
-require "chef/dsl/declare_resource"
-require "chef/mixin/shell_out"
-require "chef/mixin/which"
-require "chef/http/simple"
-require "chef/provider/noop"
-
-class Chef
- class Provider
- class AptRepository < Chef::Provider
- use_inline_resources
-
- include Chef::Mixin::ShellOut
- extend Chef::Mixin::Which
-
- provides :apt_repository do
- which("apt-get")
- end
-
- def whyrun_supported?
- true
- end
-
- def load_current_resource
- end
-
- action :add do
- unless new_resource.key.nil?
- if is_key_id?(new_resource.key) && !has_cookbook_file?(new_resource.key)
- install_key_from_keyserver
- else
- install_key_from_uri
- end
- end
-
- declare_resource(:execute, "apt-cache gencaches") do
- ignore_failure true
- action :nothing
- end
-
- declare_resource(:apt_update, new_resource.name) do
- ignore_failure true
- action :nothing
- end
-
- components = if is_ppa_url?(new_resource.uri) && new_resource.components.empty?
- "main"
- else
- new_resource.components
- end
-
- repo = build_repo(
- new_resource.uri,
- new_resource.distribution,
- components,
- new_resource.trusted,
- new_resource.arch,
- new_resource.deb_src
- )
-
- declare_resource(:file, "/etc/apt/sources.list.d/#{new_resource.name}.list") do
- owner "root"
- group "root"
- mode "0644"
- content repo
- sensitive new_resource.sensitive
- action :create
- notifies :run, "execute[apt-cache gencaches]", :immediately
- notifies :update, "apt_update[#{new_resource.name}]", :immediately if new_resource.cache_rebuild
- end
- end
-
- action :remove do
- if ::File.exist?("/etc/apt/sources.list.d/#{new_resource.name}.list")
- converge_by "Removing #{new_resource.name} repository from /etc/apt/sources.list.d/" do
- declare_resource(:file, "/etc/apt/sources.list.d/#{new_resource.name}.list") do
- sensitive new_resource.sensitive
- action :delete
- notifies :update, "apt_update[#{new_resource.name}]", :immediately if new_resource.cache_rebuild
- end
-
- declare_resource(:apt_update, new_resource.name) do
- ignore_failure true
- action :nothing
- end
-
- end
- end
- end
-
- def is_key_id?(id)
- id = id[2..-1] if id.start_with?("0x")
- id =~ /^\h+$/ && [8, 16, 40].include?(id.length)
- end
-
- def extract_fingerprints_from_cmd(cmd)
- so = shell_out(cmd)
- so.run_command
- so.stdout.split(/\n/).map do |t|
- if z = t.match(/^ +Key fingerprint = ([0-9A-F ]+)/)
- z[1].split.join
- end
- end.compact
- end
-
- def key_is_valid?(cmd, key)
- valid = true
-
- so = shell_out(cmd)
- so.run_command
- so.stdout.split(/\n/).map do |t|
- if t =~ %r{^\/#{key}.*\[expired: .*\]$}
- Chef::Log.debug "Found expired key: #{t}"
- valid = false
- break
- end
- end
-
- Chef::Log.debug "key #{key} #{valid ? "is valid" : "is not valid"}"
- valid
- end
-
- def cookbook_name
- new_resource.cookbook || new_resource.cookbook_name
- end
-
- def has_cookbook_file?(fn)
- run_context.has_cookbook_file_in_cookbook?(cookbook_name, fn)
- end
-
- def no_new_keys?(file)
- installed_keys = extract_fingerprints_from_cmd("apt-key finger")
- proposed_keys = extract_fingerprints_from_cmd("gpg --with-fingerprint #{file}")
- (installed_keys & proposed_keys).sort == proposed_keys.sort
- end
-
- def install_key_from_uri
- key_name = new_resource.key.split(%r{\/}).last
- cached_keyfile = ::File.join(Chef::Config[:file_cache_path], key_name)
- type = if new_resource.key.start_with?("http")
- :remote_file
- elsif has_cookbook_file?(new_resource.key)
- :cookbook_file
- else
- raise Chef::Exceptions::FileNotFound, "Cannot locate key file"
- end
-
- declare_resource(type, cached_keyfile) do
- source new_resource.key
- mode "0644"
- sensitive new_resource.sensitive
- action :create
- end
-
- raise "The key #{cached_keyfile} is invalid and cannot be used to verify an apt repository." unless key_is_valid?("gpg #{cached_keyfile}", "")
-
- declare_resource(:execute, "apt-key add #{cached_keyfile}") do
- sensitive new_resource.sensitive
- action :run
- not_if do
- no_new_keys?(cached_keyfile)
- end
- notifies :run, "execute[apt-cache gencaches]", :immediately
- end
- end
-
- def install_key_from_keyserver(key = new_resource.key, keyserver = new_resource.keyserver)
- cmd = "apt-key adv --recv"
- cmd << " --keyserver-options http-proxy=#{new_resource.key_proxy}" if new_resource.key_proxy
- cmd << " --keyserver "
- cmd << if keyserver.start_with?("hkp://")
- keyserver
- else
- "hkp://#{keyserver}:80"
- end
-
- cmd << " #{key}"
-
- declare_resource(:execute, "install-key #{key}") do
- command cmd
- sensitive new_resource.sensitive
- not_if do
- present = extract_fingerprints_from_cmd("apt-key finger").any? do |fp|
- fp.end_with? key.upcase
- end
- present && key_is_valid?("apt-key list", key.upcase)
- end
- notifies :run, "execute[apt-cache gencaches]", :immediately
- end
-
- raise "The key #{key} is invalid and cannot be used to verify an apt repository." unless key_is_valid?("apt-key list", key.upcase)
- end
-
- def install_ppa_key(owner, repo)
- url = "https://launchpad.net/api/1.0/~#{owner}/+archive/#{repo}"
- key_id = Chef::HTTP::Simple.new(url).get("signing_key_fingerprint").delete('"')
- install_key_from_keyserver(key_id, "keyserver.ubuntu.com")
- rescue Net::HTTPServerException => e
- raise "Could not access Launchpad ppa API: #{e.message}"
- end
-
- def is_ppa_url?(url)
- url.start_with?("ppa:")
- end
-
- def make_ppa_url(ppa)
- return unless is_ppa_url?(ppa)
- owner, repo = ppa[4..-1].split("/")
- repo ||= "ppa"
-
- install_ppa_key(owner, repo)
- "http://ppa.launchpad.net/#{owner}/#{repo}/ubuntu"
- end
-
- def build_repo(uri, distribution, components, trusted, arch, add_src = false)
- uri = make_ppa_url(uri) if is_ppa_url?(uri)
-
- uri = '"' + uri + '"' unless uri.start_with?("'", '"')
- components = Array(components).join(" ")
- options = []
- options << "arch=#{arch}" if arch
- options << "trusted=yes" if trusted
- optstr = unless options.empty?
- "[" + options.join(" ") + "]"
- end
- info = [ optstr, uri, distribution, components ].compact.join(" ")
- repo = "deb #{info}\n"
- repo << "deb-src #{info}\n" if add_src
- repo
- end
- end
- end
-end
-
-Chef::Provider::Noop.provides :apt_repository
diff --git a/lib/chef/provider/apt_update.rb b/lib/chef/provider/apt_update.rb
deleted file mode 100644
index 0320e9a83f..0000000000
--- a/lib/chef/provider/apt_update.rb
+++ /dev/null
@@ -1,87 +0,0 @@
-#
-# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright (c) 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/provider"
-require "chef/provider/noop"
-require "chef/mixin/which"
-
-class Chef
- class Provider
- class AptUpdate < Chef::Provider
- use_inline_resources
-
- extend Chef::Mixin::Which
-
- provides :apt_update do
- which("apt-get")
- end
-
- APT_CONF_DIR = "/etc/apt/apt.conf.d"
- STAMP_DIR = "/var/lib/apt/periodic"
-
- def whyrun_supported?
- true
- end
-
- def load_current_resource
- end
-
- action :periodic do
- if !apt_up_to_date?
- converge_by "update new lists of packages" do
- do_update
- end
- end
- end
-
- action :update do
- converge_by "force update new lists of packages" do
- do_update
- end
- end
-
- private
-
- # Determines whether we need to run `apt-get update`
- #
- # @return [Boolean]
- def apt_up_to_date?
- ::File.exist?("#{STAMP_DIR}/update-success-stamp") &&
- ::File.mtime("#{STAMP_DIR}/update-success-stamp") > Time.now - new_resource.frequency
- end
-
- def do_update
- [STAMP_DIR, APT_CONF_DIR].each do |d|
- declare_resource(:directory, d) do
- recursive true
- end
- end
-
- declare_resource(:file, "#{APT_CONF_DIR}/15update-stamp") do
- content "APT::Update::Post-Invoke-Success {\"touch #{STAMP_DIR}/update-success-stamp 2>/dev/null || true\";};"
- action :create_if_missing
- end
-
- declare_resource(:execute, "apt-get -q update")
- end
-
- end
- end
-end
-
-Chef::Provider::Noop.provides :apt_update
diff --git a/lib/chef/provider/batch.rb b/lib/chef/provider/batch.rb
index 0d857aaa79..af52b0a36a 100644
--- a/lib/chef/provider/batch.rb
+++ b/lib/chef/provider/batch.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Edwards (<adamed@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,30 +16,23 @@
# limitations under the License.
#
-require "chef/provider/windows_script"
+require_relative "windows_script"
class Chef
class Provider
class Batch < Chef::Provider::WindowsScript
- provides :batch, os: "windows"
-
- def initialize(new_resource, run_context)
- super(new_resource, run_context, ".bat")
- end
+ provides :batch
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}\""
+ "\"#{interpreter_path}\" #{new_resource.flags} /c \"#{script_file_path}\""
end
- def flags
- @new_resource.flags.nil? ? "/c" : new_resource.flags + " /c"
+ def script_extension
+ ".bat"
end
-
end
end
end
diff --git a/lib/chef/provider/cookbook_file.rb b/lib/chef/provider/cookbook_file.rb
index 3ca0bbd47a..82d9eebf8f 100644
--- a/lib/chef/provider/cookbook_file.rb
+++ b/lib/chef/provider/cookbook_file.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,7 @@
# limitations under the License.
#
-require "chef/provider/file"
-require "chef/deprecation/provider/cookbook_file"
-require "chef/deprecation/warnings"
+require_relative "file"
class Chef
class Provider
@@ -26,25 +24,22 @@ class Chef
provides :cookbook_file
- extend Chef::Deprecation::Warnings
- include Chef::Deprecation::Provider::CookbookFile
- add_deprecation_warnings_for(Chef::Deprecation::Provider::CookbookFile.instance_methods)
-
def initialize(new_resource, run_context)
@content_class = Chef::Provider::CookbookFile::Content
super
end
def load_current_resource
- @current_resource = Chef::Resource::CookbookFile.new(@new_resource.name)
+ @current_resource = Chef::Resource::CookbookFile.new(new_resource.name)
super
end
private
def managing_content?
- return true if @new_resource.checksum
- return true if !@new_resource.source.nil? && @action != :create_if_missing
+ return true if new_resource.checksum
+ return true if !new_resource.source.nil? && @action != :create_if_missing
+
false
end
diff --git a/lib/chef/provider/cookbook_file/content.rb b/lib/chef/provider/cookbook_file/content.rb
index 1d24dee3e7..6cc2f33f34 100644
--- a/lib/chef/provider/cookbook_file/content.rb
+++ b/lib/chef/provider/cookbook_file/content.rb
@@ -1,6 +1,6 @@
#
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/file_content_management/content_base"
-require "chef/file_content_management/tempfile"
+require_relative "../../file_content_management/content_base"
+require_relative "../../file_content_management/tempfile"
class Chef
class Provider
@@ -34,7 +34,7 @@ class Chef
else
tempfile = Chef::FileContentManagement::Tempfile.new(@new_resource).tempfile
tempfile.close
- Chef::Log.debug("#{@new_resource} staging #{file_cache_location} to #{tempfile.path}")
+ logger.trace("#{@new_resource} staging #{file_cache_location} to #{tempfile.path}")
FileUtils.cp(file_cache_location, tempfile.path)
tempfile
end
diff --git a/lib/chef/provider/cron.rb b/lib/chef/provider/cron.rb
index 7baaeec0c5..7d37f34b1a 100644
--- a/lib/chef/provider/cron.rb
+++ b/lib/chef/provider/cron.rb
@@ -15,25 +15,21 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-
-require "chef/log"
-require "chef/mixin/command"
-require "chef/provider"
+require_relative "../log"
+require_relative "../provider"
class Chef
class Provider
class Cron < Chef::Provider
- include Chef::Mixin::Command
provides :cron, os: ["!aix", "!solaris2"]
- SPECIAL_TIME_VALUES = [:reboot, :yearly, :annually, :monthly, :weekly, :daily, :midnight, :hourly]
- CRON_ATTRIBUTES = [:minute, :hour, :day, :month, :weekday, :time, :command, :mailto, :path, :shell, :home, :environment]
- WEEKDAY_SYMBOLS = [:sunday, :monday, :tuesday, :wednesday, :thursday, :friday, :saturday]
-
- CRON_PATTERN = /\A([-0-9*,\/]+)\s([-0-9*,\/]+)\s([-0-9*,\/]+)\s([-0-9*,\/]+|[a-zA-Z]{3})\s([-0-9*,\/]+|[a-zA-Z]{3})\s(.*)/
- SPECIAL_PATTERN = /\A(@(#{SPECIAL_TIME_VALUES.join('|')}))\s(.*)/
- ENV_PATTERN = /\A(\S+)=(\S*)/
+ SPECIAL_TIME_VALUES = %i{reboot yearly annually monthly weekly daily midnight hourly}.freeze
+ CRON_ATTRIBUTES = %i{minute hour day month weekday time command mailto path shell home environment}.freeze
+ CRON_PATTERN = %r{\A([-0-9*,/]+)\s([-0-9*,/]+)\s([-0-9*,/]+)\s([-0-9*,/]+|[a-zA-Z]{3})\s([-0-9*,/]+|[a-zA-Z]{3})\s(.*)}.freeze
+ SPECIAL_PATTERN = /\A(@(#{SPECIAL_TIME_VALUES.join('|')}))\s(.*)/.freeze
+ ENV_PATTERN = /\A(\S+)=(\S*)/.freeze
+ ENVIRONMENT_PROPERTIES = %w{MAILTO PATH SHELL HOME}.freeze
def initialize(new_resource, run_context)
super(new_resource, run_context)
@@ -42,21 +38,17 @@ class Chef
end
attr_accessor :cron_exists, :cron_empty
- def whyrun_supported?
- true
- end
-
def load_current_resource
crontab_lines = []
- @current_resource = Chef::Resource::Cron.new(@new_resource.name)
- @current_resource.user(@new_resource.user)
+ @current_resource = Chef::Resource::Cron.new(new_resource.name)
+ current_resource.user(new_resource.user)
@cron_exists = false
if crontab = read_crontab
cron_found = false
crontab.each_line do |line|
case line.chomp
- when "# Chef Name: #{@new_resource.name}"
- Chef::Log.debug("Found cron '#{@new_resource.name}'")
+ when "# Chef Name: #{new_resource.name}"
+ logger.trace("Found cron '#{new_resource.name}'")
cron_found = true
@cron_exists = true
next
@@ -65,18 +57,18 @@ class Chef
next
when SPECIAL_PATTERN
if cron_found
- @current_resource.time($2.to_sym)
- @current_resource.command($3)
+ current_resource.time($2.to_sym)
+ current_resource.command($3)
cron_found = false
end
when CRON_PATTERN
if cron_found
- @current_resource.minute($1)
- @current_resource.hour($2)
- @current_resource.day($3)
- @current_resource.month($4)
- @current_resource.weekday($5)
- @current_resource.command($6)
+ current_resource.minute($1)
+ current_resource.hour($2)
+ current_resource.day($3)
+ current_resource.month($4)
+ current_resource.weekday($5)
+ current_resource.command($6)
cron_found = false
end
next
@@ -85,48 +77,42 @@ class Chef
next
end
end
- Chef::Log.debug("Cron '#{@new_resource.name}' not found") unless @cron_exists
+ logger.trace("Cron '#{new_resource.name}' not found") unless @cron_exists
else
- Chef::Log.debug("Cron empty for '#{@new_resource.user}'")
+ logger.trace("Cron empty for '#{new_resource.user}'")
@cron_empty = true
end
- @current_resource
+ current_resource
end
def cron_different?
CRON_ATTRIBUTES.any? do |cron_var|
- @new_resource.send(cron_var) != @current_resource.send(cron_var)
+ new_resource.send(cron_var) != current_resource.send(cron_var)
end
end
- def action_create
- crontab = String.new
- newcron = String.new
+ action :create do
+ crontab = ""
+ newcron = ""
cron_found = false
newcron = get_crontab_entry
if @cron_exists
unless cron_different?
- Chef::Log.debug("Skipping existing cron entry '#{@new_resource.name}'")
+ logger.trace("Skipping existing cron entry '#{new_resource.name}'")
return
end
read_crontab.each_line do |line|
case line.chomp
- when "# Chef Name: #{@new_resource.name}"
+ when "# Chef Name: #{new_resource.name}"
cron_found = true
next
when ENV_PATTERN
crontab << line unless cron_found
next
- when SPECIAL_PATTERN
- if cron_found
- cron_found = false
- crontab << newcron
- next
- end
- when CRON_PATTERN
+ when SPECIAL_PATTERN, CRON_PATTERN
if cron_found
cron_found = false
crontab << newcron
@@ -144,39 +130,34 @@ class Chef
# Handle edge case where the Chef comment is the last line in the current crontab
crontab << newcron if cron_found
- converge_by("update crontab entry for #{@new_resource}") do
+ converge_by("update crontab entry for #{new_resource}") do
write_crontab crontab
- Chef::Log.info("#{@new_resource} updated crontab entry")
+ logger.info("#{new_resource} updated crontab entry")
end
else
crontab = read_crontab unless @cron_empty
crontab << newcron
- converge_by("add crontab entry for #{@new_resource}") do
+ converge_by("add crontab entry for #{new_resource}") do
write_crontab crontab
- Chef::Log.info("#{@new_resource} added crontab entry")
+ logger.info("#{new_resource} added crontab entry")
end
end
end
- def action_delete
+ action :delete do
if @cron_exists
- crontab = String.new
+ crontab = ""
cron_found = false
read_crontab.each_line do |line|
case line.chomp
- when "# Chef Name: #{@new_resource.name}"
+ when "# Chef Name: #{new_resource.name}"
cron_found = true
next
when ENV_PATTERN
next if cron_found
- when SPECIAL_PATTERN
- if cron_found
- cron_found = false
- next
- end
- when CRON_PATTERN
+ when SPECIAL_PATTERN, CRON_PATTERN
if cron_found
cron_found = false
next
@@ -187,10 +168,10 @@ class Chef
end
crontab << line
end
- description = cron_found ? "remove #{@new_resource.name} from crontab" : "save unmodified crontab"
+ description = cron_found ? "remove #{new_resource.name} from crontab" : "save unmodified crontab"
converge_by(description) do
write_crontab crontab
- Chef::Log.info("#{@new_resource} deleted crontab entry")
+ logger.info("#{new_resource} deleted crontab entry")
end
end
end
@@ -198,65 +179,101 @@ class Chef
private
def set_environment_var(attr_name, attr_value)
- if %w{MAILTO PATH SHELL HOME}.include?(attr_name)
- @current_resource.send(attr_name.downcase.to_sym, attr_value)
+ if ENVIRONMENT_PROPERTIES.include?(attr_name)
+ current_resource.send(attr_name.downcase.to_sym, attr_value.gsub(/^"|"$/, ""))
else
- @current_resource.environment(@current_resource.environment.merge(attr_name => attr_value))
+ current_resource.environment(current_resource.environment.merge(attr_name => attr_value))
end
end
def read_crontab
- crontab = nil
- status = popen4("crontab -l -u #{@new_resource.user}") do |pid, stdin, stdout, stderr|
- crontab = stdout.read
- end
- if status.exitstatus > 1
- raise Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: #{status.exitstatus}"
- end
- crontab
+ so = shell_out!("crontab -l -u #{new_resource.user}", returns: [0, 1])
+ return nil if so.exitstatus == 1
+
+ so.stdout
+ rescue => e
+ raise Chef::Exceptions::Cron, "Error determining state of #{new_resource.name}, error: #{e}"
end
def write_crontab(crontab)
write_exception = false
- status = popen4("crontab -u #{@new_resource.user} -", :waitlast => true) do |pid, stdin, stdout, stderr|
- begin
- stdin.write crontab
- rescue Errno::EPIPE => e
- # popen4 could yield while child has already died.
- write_exception = true
- Chef::Log.debug("#{e.message}")
- end
- end
- if status.exitstatus > 0 || write_exception
- raise Chef::Exceptions::Cron, "Error updating state of #{@new_resource.name}, exit: #{status.exitstatus}"
- end
+ so = shell_out!("crontab -u #{new_resource.user} -", input: crontab)
+ rescue => e
+ raise Chef::Exceptions::Cron, "Error updating state of #{new_resource.name}, error: #{e}"
end
- def get_crontab_entry
- newcron = ""
- newcron << "# Chef Name: #{new_resource.name}\n"
- [ :mailto, :path, :shell, :home ].each do |v|
- newcron << "#{v.to_s.upcase}=\"#{@new_resource.send(v)}\"\n" if @new_resource.send(v)
- end
- @new_resource.environment.each do |name, value|
- newcron << "#{name}=#{value}\n"
+ #
+ # @return [String] The string of Env Variables containing line breaks.
+ #
+ def env_var_str
+ str = []
+ %i{mailto path shell home}.each do |v|
+ str << "#{v.to_s.upcase}=\"#{new_resource.send(v)}\"" if new_resource.send(v)
end
- if @new_resource.time
- newcron << "@#{@new_resource.time} #{@new_resource.command}\n"
- else
- newcron << "#{@new_resource.minute} #{@new_resource.hour} #{@new_resource.day} #{@new_resource.month} #{@new_resource.weekday} #{@new_resource.command}\n"
+ new_resource.environment.each do |name, value|
+ if ENVIRONMENT_PROPERTIES.include?(name)
+ unless new_resource.property_is_set?(name.downcase)
+ logger.warn("#{new_resource.name}: the environment property contains the '#{name}' variable, which should be set separately as a property.")
+ new_resource.send(name.downcase.to_sym, value.gsub(/^"|"$/, ""))
+ new_resource.environment.delete(name)
+ str << "#{name.to_s.upcase}=\"#{value}\""
+ else
+ raise Chef::Exceptions::Cron, "#{new_resource.name}: the '#{name}' property is set and environment property also contains the '#{name}' variable. Remove the variable from the environment property."
+ end
+ else
+ str << "#{name}=#{value}"
+ end
end
- newcron
+ str.join("\n")
end
- def weekday_in_crontab
- weekday_in_crontab = WEEKDAY_SYMBOLS.index(@new_resource.weekday)
- if weekday_in_crontab.nil?
- @new_resource.weekday
+ #
+ # @return [String] The Cron time string consisting five fields that Cron converts into a time interval.
+ #
+ def duration_str
+ if new_resource.time
+ "@#{new_resource.time}"
else
- weekday_in_crontab.to_s
+ "#{new_resource.minute} #{new_resource.hour} #{new_resource.day} #{new_resource.month} #{new_resource.weekday}"
end
end
+
+ #
+ # @return [String] The timeout command string formed as per time_out property.
+ #
+ def time_out_str
+ return "" if new_resource.time_out.empty?
+
+ str = " timeout"
+ str << " --preserve-status" if new_resource.time_out["preserve-status"].to_s.casecmp("true") == 0
+ str << " --foreground" if new_resource.time_out["foreground"].to_s.casecmp("true") == 0
+ str << " --kill-after #{new_resource.time_out["kill-after"]}" if new_resource.time_out["kill-after"]
+ str << " --signal #{new_resource.time_out["signal"]}" if new_resource.time_out["signal"]
+ str << " #{new_resource.time_out["duration"]};"
+ str
+ end
+
+ #
+ # @return [String] The command to be executed. The new line at the end has been added purposefully.
+ #
+ def cmd_str
+ " #{new_resource.command}\n"
+ end
+
+ # Concatenates various information and formulates a complete string that
+ # could be written in the crontab
+ #
+ # @return [String] A crontab string formed as per the user inputs.
+ #
+ def get_crontab_entry
+ # Initialize
+ newcron = []
+ newcron << "# Chef Name: #{new_resource.name}"
+ newcron << env_var_str unless env_var_str.empty?
+ newcron << duration_str + time_out_str + cmd_str
+
+ newcron.join("\n")
+ end
end
end
end
diff --git a/lib/chef/provider/cron/aix.rb b/lib/chef/provider/cron/aix.rb
index 015c1f2096..c1efafa024 100644
--- a/lib/chef/provider/cron/aix.rb
+++ b/lib/chef/provider/cron/aix.rb
@@ -1,6 +1,6 @@
#
# Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/provider/cron/unix"
+require_relative "unix"
class Chef
class Provider
@@ -33,8 +33,11 @@ class Chef
raise Chef::Exceptions::Cron, "Aix cron entry does not support environment variables. Please set them in script and use script in cron."
end
- newcron = ""
- newcron << "# Chef Name: #{new_resource.name}\n"
+ if time_out_set?
+ raise Chef::Exceptions::Cron, "Aix cron entry does not support timeout."
+ end
+
+ newcron = "# Chef Name: #{new_resource.name}\n"
newcron << "#{@new_resource.minute} #{@new_resource.hour} #{@new_resource.day} #{@new_resource.month} #{@new_resource.weekday}"
newcron << " #{@new_resource.command}\n"
@@ -44,6 +47,10 @@ class Chef
def env_vars_are_set?
@new_resource.environment.length > 0 || !@new_resource.mailto.nil? || !@new_resource.path.nil? || !@new_resource.shell.nil? || !@new_resource.home.nil?
end
+
+ def time_out_set?
+ !@new_resource.time_out.empty?
+ end
end
end
end
diff --git a/lib/chef/provider/cron/solaris.rb b/lib/chef/provider/cron/solaris.rb
index 58d6637674..02ad578b8a 100644
--- a/lib/chef/provider/cron/solaris.rb
+++ b/lib/chef/provider/cron/solaris.rb
@@ -1,6 +1,6 @@
#
# Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/provider/cron/unix"
+require_relative "unix"
# Just to create an alias so 'Chef::Provider::Cron::Solaris' is exposed and accessible to existing consumers of class.
Chef::Provider::Cron::Solaris = Chef::Provider::Cron::Unix
diff --git a/lib/chef/provider/cron/unix.rb b/lib/chef/provider/cron/unix.rb
index 108e73c9d3..cb3a2f9a61 100644
--- a/lib/chef/provider/cron/unix.rb
+++ b/lib/chef/provider/cron/unix.rb
@@ -18,30 +18,29 @@
# limitations under the License.
#
-require "chef/log"
-require "chef/provider"
-require "chef/provider/cron"
+require_relative "../../log"
+require_relative "../../provider"
+require_relative "../cron"
class Chef
class Provider
class Cron
class Unix < Chef::Provider::Cron
- include Chef::Mixin::ShellOut
-
provides :cron, os: "solaris2"
private
def read_crontab
- crontab = shell_out("/usr/bin/crontab -l", :user => @new_resource.user)
+ crontab = shell_out(%w{/usr/bin/crontab -l}, user: @new_resource.user)
status = crontab.status.exitstatus
- Chef::Log.debug crontab.format_for_exception if status > 0
+ logger.trace crontab.format_for_exception if status > 0
if status > 1
raise Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: #{status}"
end
return nil if status > 0
+
crontab.stdout.chomp << "\n"
end
@@ -53,7 +52,7 @@ class Chef
exit_status = 0
error_message = ""
begin
- crontab_write = shell_out("/usr/bin/crontab #{tempcron.path}", :user => @new_resource.user)
+ crontab_write = shell_out("/usr/bin/crontab", tempcron.path, user: @new_resource.user)
stderr = crontab_write.stderr
exit_status = crontab_write.status.exitstatus
# solaris9, 10 on some failures for example invalid 'mins' in crontab fails with exit code of zero :(
@@ -62,12 +61,12 @@ class Chef
exit_status = 1
end
rescue Chef::Exceptions::Exec => e
- Chef::Log.debug(e.message)
+ logger.trace(e.message)
exit_status = 1
error_message = e.message
rescue ArgumentError => e
# usually raised on invalid user.
- Chef::Log.debug(e.message)
+ logger.trace(e.message)
exit_status = 1
error_message = e.message
end
diff --git a/lib/chef/provider/deploy.rb b/lib/chef/provider/deploy.rb
deleted file mode 100644
index 4c758033ac..0000000000
--- a/lib/chef/provider/deploy.rb
+++ /dev/null
@@ -1,476 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@kallistec.com>)
-# Copyright:: Copyright 2008-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/mixin/command"
-require "chef/mixin/from_file"
-require "chef/provider/git"
-require "chef/provider/subversion"
-require "chef/dsl/recipe"
-require "chef/util/path_helper"
-
-class Chef
- class Provider
- class Deploy < Chef::Provider
-
- include Chef::DSL::Recipe
- include Chef::Mixin::FromFile
- include Chef::Mixin::Command
-
- attr_reader :scm_provider, :release_path, :shared_path, :previous_release_path
-
- def initialize(new_resource, run_context)
- super(new_resource, run_context)
-
- # will resolve to either git or svn based on resource attributes,
- # and will create a resource corresponding to that provider
- @scm_provider = new_resource.scm_provider.new(new_resource, run_context)
-
- # @configuration is not used by Deploy, it is only for backwards compat with
- # chef-deploy or capistrano hooks that might use it to get environment information
- @configuration = @new_resource.to_hash
- @configuration[:environment] = @configuration[:environment] && @configuration[:environment]["RAILS_ENV"]
- end
-
- def whyrun_supported?
- true
- end
-
- def load_current_resource
- @scm_provider.load_current_resource
- @release_path = @new_resource.deploy_to + "/releases/#{release_slug}"
- @shared_path = @new_resource.shared_path
- end
-
- def sudo(command, &block)
- execute(command, &block)
- end
-
- def run(command, &block)
- exec = execute(command, &block)
- exec.user(@new_resource.user) if @new_resource.user
- exec.group(@new_resource.group) if @new_resource.group
- exec.cwd(release_path) unless exec.cwd
- exec.environment(@new_resource.environment) unless exec.environment
- converge_by("execute #{command}") do
- exec
- end
- end
-
- def define_resource_requirements
- requirements.assert(:rollback) do |a|
- a.assertion { all_releases[-2] }
- a.failure_message(RuntimeError, "There is no release to rollback to!")
- #There is no reason to assume 2 deployments in a single chef run, hence fails in whyrun.
- end
-
- [ @new_resource.before_migrate, @new_resource.before_symlink,
- @new_resource.before_restart, @new_resource.after_restart ].each do |script|
- requirements.assert(:deploy, :force_deploy) do |a|
- callback_file = "#{release_path}/#{script}"
- a.assertion do
- if script && script.class == String
- ::File.exist?(callback_file)
- else
- true
- end
- end
- a.failure_message(RuntimeError, "Can't find your callback file #{callback_file}")
- a.whyrun("Would assume callback file #{callback_file} included in release")
- end
- end
- end
-
- def action_deploy
- save_release_state
- if deployed?(release_path )
- if current_release?(release_path )
- Chef::Log.debug("#{@new_resource} is the latest version")
- else
- rollback_to release_path
- end
- else
-
- with_rollback_on_error do
- deploy
- end
- end
- end
-
- def action_force_deploy
- if deployed?(release_path)
- converge_by("delete deployed app at #{release_path} prior to force-deploy") do
- Chef::Log.info("Already deployed app at #{release_path}, forcing.")
- FileUtils.rm_rf(release_path)
- Chef::Log.info("#{@new_resource} forcing deploy of already deployed app at #{release_path}")
- end
- end
-
- # Alternatives:
- # * Move release_path directory before deploy and move it back when error occurs
- # * Rollback to previous commit
- # * Do nothing - because deploy is force, it will be retried in short time
- # Because last is simplest, keep it
- deploy
- end
-
- def action_rollback
- rollback_to all_releases[-2]
- end
-
- def rollback_to(target_release_path)
- @release_path = target_release_path
-
- rp_index = all_releases.index(release_path)
- releases_to_nuke = all_releases[(rp_index + 1)..-1]
-
- rollback
-
- releases_to_nuke.each do |i|
- converge_by("roll back by removing release #{i}") do
- Chef::Log.info "#{@new_resource} removing release: #{i}"
- FileUtils.rm_rf i
- end
- release_deleted(i)
- end
- end
-
- def deploy
- verify_directories_exist
- update_cached_repo # no converge-by - scm provider will dothis
- enforce_ownership
- copy_cached_repo
- install_gems
- enforce_ownership
- callback(:before_migrate, @new_resource.before_migrate)
- migrate
- callback(:before_symlink, @new_resource.before_symlink)
- symlink
- callback(:before_restart, @new_resource.before_restart)
- restart
- callback(:after_restart, @new_resource.after_restart)
- cleanup!
- Chef::Log.info "#{@new_resource} deployed to #{@new_resource.deploy_to}"
- end
-
- def rollback
- Chef::Log.info "#{@new_resource} rolling back to previous release #{release_path}"
- symlink
- Chef::Log.info "#{@new_resource} restarting with previous release"
- restart
- end
-
- def callback(what, callback_code = nil)
- @collection = Chef::ResourceCollection.new
- case callback_code
- when Proc
- Chef::Log.info "#{@new_resource} running callback #{what}"
- recipe_eval(&callback_code)
- when String
- run_callback_from_file("#{release_path}/#{callback_code}")
- when nil
- run_callback_from_file("#{release_path}/deploy/#{what}.rb")
- end
- end
-
- def migrate
- run_symlinks_before_migrate
-
- if @new_resource.migrate
- enforce_ownership
-
- environment = @new_resource.environment
- env_info = environment && environment.map do |key_and_val|
- "#{key_and_val.first}='#{key_and_val.last}'"
- end.join(" ")
-
- converge_by("execute migration command #{@new_resource.migration_command}") do
- Chef::Log.info "#{@new_resource} migrating #{@new_resource.user} with environment #{env_info}"
- shell_out!(@new_resource.migration_command, run_options(:cwd => release_path, :log_level => :info))
- end
- end
- end
-
- def symlink
- purge_tempfiles_from_current_release
- link_tempfiles_to_current_release
- link_current_release_to_production
- Chef::Log.info "#{@new_resource} updated symlinks"
- end
-
- def restart
- if restart_cmd = @new_resource.restart_command
- if restart_cmd.kind_of?(Proc)
- Chef::Log.info("#{@new_resource} restarting app with embedded recipe")
- recipe_eval(&restart_cmd)
- else
- converge_by("restart app using command #{@new_resource.restart_command}") do
- Chef::Log.info("#{@new_resource} restarting app")
- shell_out!(@new_resource.restart_command, run_options(:cwd => @new_resource.current_path))
- end
- end
- end
- end
-
- def cleanup!
- converge_by("update release history data") do
- release_created(release_path)
- end
-
- chop = -1 - @new_resource.keep_releases
- all_releases[0..chop].each do |old_release|
- converge_by("remove old release #{old_release}") do
- Chef::Log.info "#{@new_resource} removing old release #{old_release}"
- FileUtils.rm_rf(old_release)
- end
- release_deleted(old_release)
- end
- end
-
- def all_releases
- Dir.glob(Chef::Util::PathHelper.escape_glob_dir(@new_resource.deploy_to) + "/releases/*").sort
- end
-
- def update_cached_repo
- if @new_resource.svn_force_export
- # TODO assertion, non-recoverable - @scm_provider must be svn if force_export?
- svn_force_export
- else
- run_scm_sync
- end
- end
-
- def run_scm_sync
- @scm_provider.run_action(:sync)
- end
-
- def svn_force_export
- Chef::Log.info "#{@new_resource} exporting source repository"
- @scm_provider.run_action(:force_export)
- end
-
- def copy_cached_repo
- target_dir_path = @new_resource.deploy_to + "/releases"
- converge_by("deploy from repo to #{target_dir_path} ") do
- FileUtils.rm_rf(release_path) if ::File.exist?(release_path)
- FileUtils.mkdir_p(target_dir_path)
- FileUtils.cp_r(::File.join(@new_resource.destination, "."), release_path, :preserve => true)
- Chef::Log.info "#{@new_resource} copied the cached checkout to #{release_path}"
- end
- end
-
- def enforce_ownership
- 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, :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
- end
-
- def verify_directories_exist
- create_dir_unless_exists(@new_resource.deploy_to)
- create_dir_unless_exists(@new_resource.shared_path)
- end
-
- def link_current_release_to_production
- converge_by(["remove existing link at #{@new_resource.current_path}",
- "link release #{release_path} into production at #{@new_resource.current_path}"]) do
- FileUtils.rm_f(@new_resource.current_path)
- begin
- FileUtils.ln_sf(release_path, @new_resource.current_path)
- rescue => e
- raise Chef::Exceptions::FileNotFound.new("Cannot symlink current release to production: #{e.message}")
- end
- Chef::Log.info "#{@new_resource} linked release #{release_path} into production at #{@new_resource.current_path}"
- end
- enforce_ownership
- end
-
- def run_symlinks_before_migrate
- links_info = @new_resource.symlink_before_migrate.map { |src, dst| "#{src} => #{dst}" }.join(", ")
- converge_by("make pre-migration symlinks: #{links_info}") do
- @new_resource.symlink_before_migrate.each do |src, dest|
- begin
- FileUtils.ln_sf(@new_resource.shared_path + "/#{src}", release_path + "/#{dest}")
- rescue => e
- raise Chef::Exceptions::FileNotFound.new("Cannot symlink #{@new_resource.shared_path}/#{src} to #{release_path}/#{dest} before migrate: #{e.message}")
- end
- end
- Chef::Log.info "#{@new_resource} made pre-migration symlinks"
- end
- end
-
- def link_tempfiles_to_current_release
- dirs_info = @new_resource.create_dirs_before_symlink.join(",")
- @new_resource.create_dirs_before_symlink.each do |dir|
- create_dir_unless_exists(release_path + "/#{dir}")
- end
- Chef::Log.info("#{@new_resource} created directories before symlinking: #{dirs_info}")
-
- links_info = @new_resource.symlinks.map { |src, dst| "#{src} => #{dst}" }.join(", ")
- converge_by("link shared paths into current release: #{links_info}") do
- @new_resource.symlinks.each do |src, dest|
- begin
- FileUtils.ln_sf(::File.join(@new_resource.shared_path, src), ::File.join(release_path, dest))
- rescue => e
- raise Chef::Exceptions::FileNotFound.new("Cannot symlink shared data #{::File.join(@new_resource.shared_path, src)} to #{::File.join(release_path, dest)}: #{e.message}")
- end
- end
- Chef::Log.info("#{@new_resource} linked shared paths into current release: #{links_info}")
- end
- run_symlinks_before_migrate
- enforce_ownership
- end
-
- def create_dirs_before_symlink
- end
-
- def purge_tempfiles_from_current_release
- log_info = @new_resource.purge_before_symlink.join(", ")
- converge_by("purge directories in checkout #{log_info}") do
- @new_resource.purge_before_symlink.each { |dir| FileUtils.rm_rf(release_path + "/#{dir}") }
- Chef::Log.info("#{@new_resource} purged directories in checkout #{log_info}")
- end
- end
-
- protected
-
- # Internal callback, called after copy_cached_repo.
- # Override if you need to keep state externally.
- # Note that YOU are responsible for implementing whyrun-friendly behavior
- # in any actions you take in this callback.
- def release_created(release_path)
- end
-
- # Note that YOU are responsible for using appropriate whyrun nomenclature
- # Override if you need to keep state externally.
- # Note that YOU are responsible for implementing whyrun-friendly behavior
- # in any actions you take in this callback.
- def release_deleted(release_path)
- end
-
- def release_slug
- raise Chef::Exceptions::Override, "You must override release_slug in #{self}"
- end
-
- def install_gems
- gem_resource_collection_runner.converge
- end
-
- def gem_resource_collection_runner
- 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
- return [] unless ::File.exist?("#{release_path}/gems.yml")
- gems = YAML.load(IO.read("#{release_path}/gems.yml"))
-
- gems.map do |g|
- r = Chef::Resource::GemPackage.new(g[:name], run_context)
- r.version g[:version]
- r.action :install
- r.source "http://gems.github.com"
- r
- end
- end
-
- def run_options(run_opts = {})
- run_opts[:user] = @new_resource.user if @new_resource.user
- run_opts[:group] = @new_resource.group if @new_resource.group
- run_opts[:environment] = @new_resource.environment if @new_resource.environment
- run_opts[:log_tag] = @new_resource.to_s
- run_opts[:log_level] ||= :debug
- if run_opts[:log_level] == :info
- if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.info?
- run_opts[:live_stream] = STDOUT
- end
- end
- run_opts
- end
-
- def run_callback_from_file(callback_file)
- Chef::Log.info "#{@new_resource} queueing checkdeploy hook #{callback_file}"
- recipe_eval do
- Dir.chdir(release_path) do
- from_file(callback_file) if ::File.exist?(callback_file)
- end
- end
- end
-
- def create_dir_unless_exists(dir)
- if ::File.directory?(dir)
- Chef::Log.debug "#{@new_resource} not creating #{dir} because it already exists"
- return false
- end
- converge_by("create new directory #{dir}") do
- begin
- FileUtils.mkdir_p(dir)
- Chef::Log.debug "#{@new_resource} created directory #{dir}"
- if @new_resource.user
- FileUtils.chown(@new_resource.user, nil, dir)
- Chef::Log.debug("#{@new_resource} set user to #{@new_resource.user} for #{dir}")
- end
- if @new_resource.group
- FileUtils.chown(nil, @new_resource.group, dir)
- Chef::Log.debug("#{@new_resource} set group to #{@new_resource.group} for #{dir}")
- end
- rescue => e
- raise Chef::Exceptions::FileNotFound.new("Cannot create directory #{dir}: #{e.message}")
- end
- end
- end
-
- def with_rollback_on_error
- yield
- rescue ::Exception => e
- if @new_resource.rollback_on_error
- Chef::Log.warn "Error on deploying #{release_path}: #{e.message}"
- failed_release = release_path
-
- if previous_release_path
- @release_path = previous_release_path
- rollback
- end
- converge_by("remove failed deploy #{failed_release}") do
- Chef::Log.info "Removing failed deploy #{failed_release}"
- FileUtils.rm_rf failed_release
- end
- release_deleted(failed_release)
- end
-
- raise
- end
-
- def save_release_state
- if ::File.exists?(@new_resource.current_path)
- release = ::File.readlink(@new_resource.current_path)
- @previous_release_path = release if ::File.exists?(release)
- end
- end
-
- def deployed?(release)
- all_releases.include?(release)
- end
-
- def current_release?(release)
- @previous_release_path == release
- end
- end
- end
-end
diff --git a/lib/chef/provider/deploy/revision.rb b/lib/chef/provider/deploy/revision.rb
deleted file mode 100644
index f61e439486..0000000000
--- a/lib/chef/provider/deploy/revision.rb
+++ /dev/null
@@ -1,109 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@kallistec.com>)
-# Author:: Tim Hinderliter (<tim@chef.io>)
-# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright 2009-2016, Daniel DeLeo
-# Copyright:: Copyright 2010-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/provider"
-require "chef/provider/deploy"
-require "chef/json_compat"
-
-class Chef
- class Provider
- class Deploy
- class Revision < Chef::Provider::Deploy
- provides :deploy_revision
- provides :deploy_branch
-
- def all_releases
- sorted_releases
- end
-
- def action_deploy
- validate_release_history!
- super
- end
-
- def cleanup!
- super
-
- known_releases = sorted_releases
-
- Dir["#{Chef::Util::PathHelper.escape_glob_dir(new_resource.deploy_to)}/releases/*"].each do |release_dir|
- unless known_releases.include?(release_dir)
- converge_by("Remove unknown release in #{release_dir}") do
- FileUtils.rm_rf(release_dir)
- end
- end
- end
- end
-
- protected
-
- def release_created(release)
- sorted_releases { |r| r.delete(release); r << release }
- end
-
- def release_deleted(release)
- sorted_releases { |r| r.delete(release) }
- end
-
- def release_slug
- scm_provider.revision_slug
- end
-
- private
-
- def sorted_releases
- cache = load_cache
- if block_given?
- yield cache
- save_cache(cache)
- end
- cache
- end
-
- def validate_release_history!
- sorted_releases do |release_list|
- release_list.each do |path|
- release_list.delete(path) unless ::File.exist?(path)
- end
- end
- end
-
- def sorted_releases_from_filesystem
- Dir.glob(Chef::Util::PathHelper.escape_glob_dir(new_resource.deploy_to) + "/releases/*").sort_by { |d| ::File.ctime(d) }
- end
-
- def load_cache
- begin
- Chef::JSONCompat.parse(Chef::FileCache.load("revision-deploys/#{new_resource.name}"))
- rescue Chef::Exceptions::FileNotFound
- sorted_releases_from_filesystem
- end
- end
-
- def save_cache(cache)
- Chef::FileCache.store("revision-deploys/#{new_resource.name}", Chef::JSONCompat.to_json(cache))
- cache
- end
-
- end
- end
- end
-end
diff --git a/lib/chef/provider/directory.rb b/lib/chef/provider/directory.rb
index 619ab5d8b6..555340d91e 100644
--- a/lib/chef/provider/directory.rb
+++ b/lib/chef/provider/directory.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -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_relative "../config"
+require_relative "../log"
+require_relative "../resource/directory"
+require_relative "../provider"
+require_relative "file"
+require "fileutils" unless defined?(FileUtils)
class Chef
class Provider
@@ -29,17 +29,13 @@ class Chef
provides :directory
- def whyrun_supported?
- true
- end
-
def load_current_resource
- @current_resource = Chef::Resource::Directory.new(@new_resource.name)
- @current_resource.path(@new_resource.path)
- if ::File.exists?(@current_resource.path) && @action != :create_if_missing
- load_resource_attributes_from_file(@current_resource)
+ @current_resource = Chef::Resource::Directory.new(new_resource.name)
+ current_resource.path(new_resource.path)
+ if ::File.exists?(current_resource.path) && @action != :create_if_missing
+ load_resource_attributes_from_file(current_resource)
end
- @current_resource
+ current_resource
end
def define_resource_requirements
@@ -49,9 +45,9 @@ class Chef
requirements.assert(:create) do |a|
# Make sure the parent dir exists, or else fail.
# for why run, print a message explaining the potential error.
- parent_directory = ::File.dirname(@new_resource.path)
+ parent_directory = ::File.dirname(new_resource.path)
a.assertion do
- if @new_resource.recursive
+ if new_resource.recursive
does_parent_exist = lambda do |base_dir|
base_dir = ::File.dirname(base_dir)
if ::File.exist?(base_dir)
@@ -60,20 +56,20 @@ class Chef
does_parent_exist.call(base_dir)
end
end
- does_parent_exist.call(@new_resource.path)
+ does_parent_exist.call(new_resource.path)
else
::File.directory?(parent_directory)
end
end
- a.failure_message(Chef::Exceptions::EnclosingDirectoryDoesNotExist, "Parent directory #{parent_directory} does not exist, cannot create #{@new_resource.path}")
+ a.failure_message(Chef::Exceptions::EnclosingDirectoryDoesNotExist, "Parent directory #{parent_directory} does not exist, cannot create #{new_resource.path}")
a.whyrun("Assuming directory #{parent_directory} would have been created")
end
requirements.assert(:create) do |a|
- parent_directory = ::File.dirname(@new_resource.path)
+ parent_directory = ::File.dirname(new_resource.path)
a.assertion do
- if @new_resource.recursive
- # find the lowest-level directory in @new_resource.path that already exists
+ if new_resource.recursive
+ # find the lowest-level directory in new_resource.path that already exists
# make sure we have write permissions to that directory
is_parent_writable = lambda do |base_dir|
base_dir = ::File.dirname(base_dir)
@@ -89,7 +85,7 @@ class Chef
is_parent_writable.call(base_dir)
end
end
- is_parent_writable.call(@new_resource.path)
+ is_parent_writable.call(new_resource.path)
else
# in why run mode & parent directory does not exist no permissions check is required
# If not in why run, permissions must be valid and we rely on prior assertion that dir exists
@@ -97,7 +93,7 @@ class Chef
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)
+ Chef::Util::PathHelper.writable_sip_path?(new_resource.path)
else
false
end
@@ -107,51 +103,51 @@ class Chef
end
end
a.failure_message(Chef::Exceptions::InsufficientPermissions,
- "Cannot create #{@new_resource} at #{@new_resource.path} due to insufficient permissions")
+ "Cannot create #{new_resource} at #{new_resource.path} due to insufficient permissions")
end
requirements.assert(:delete) do |a|
a.assertion do
- if ::File.exists?(@new_resource.path)
- ::File.directory?(@new_resource.path) && Chef::FileAccessControl.writable?(@new_resource.path)
+ if ::File.exists?(new_resource.path)
+ ::File.directory?(new_resource.path) && Chef::FileAccessControl.writable?(new_resource.path)
else
true
end
end
- a.failure_message(RuntimeError, "Cannot delete #{@new_resource} at #{@new_resource.path}!")
+ a.failure_message(RuntimeError, "Cannot delete #{new_resource} at #{new_resource.path}!")
# No why-run handling here:
# * if we don't have permissions, this is unlikely to be changed earlier in the run
# * if the target is a file (not a dir), there's no reasonable path by which this would have been changed
end
end
- def action_create
- unless ::File.exists?(@new_resource.path)
- converge_by("create new directory #{@new_resource.path}") do
- if @new_resource.recursive == true
- ::FileUtils.mkdir_p(@new_resource.path)
+ action :create do
+ unless ::File.exists?(new_resource.path)
+ converge_by("create new directory #{new_resource.path}") do
+ if new_resource.recursive == true
+ ::FileUtils.mkdir_p(new_resource.path)
else
- ::Dir.mkdir(@new_resource.path)
+ ::Dir.mkdir(new_resource.path)
end
- Chef::Log.info("#{@new_resource} created directory #{@new_resource.path}")
+ logger.info("#{new_resource} created directory #{new_resource.path}")
end
end
do_acl_changes
do_selinux(true)
- load_resource_attributes_from_file(@new_resource)
+ load_resource_attributes_from_file(new_resource) unless Chef::Config[:why_run]
end
- def action_delete
- if ::File.exists?(@new_resource.path)
- converge_by("delete existing directory #{@new_resource.path}") do
- if @new_resource.recursive == true
+ action :delete do
+ if ::File.exists?(new_resource.path)
+ converge_by("delete existing directory #{new_resource.path}") do
+ if new_resource.recursive == true
# we don't use rm_rf here because it masks all errors, including
- # IO errors or permission errors that would prvent the deletion
- FileUtils.rm_r(@new_resource.path)
- Chef::Log.info("#{@new_resource} deleted #{@new_resource.path} recursively")
+ # IO errors or permission errors that would prevent the deletion
+ FileUtils.rm_r(new_resource.path)
+ logger.info("#{new_resource} deleted #{new_resource.path} recursively")
else
- ::Dir.delete(@new_resource.path)
- Chef::Log.info("#{@new_resource} deleted #{@new_resource.path}")
+ ::Dir.delete(new_resource.path)
+ logger.info("#{new_resource} deleted #{new_resource.path}")
end
end
end
diff --git a/lib/chef/provider/dsc_resource.rb b/lib/chef/provider/dsc_resource.rb
index 0f25065925..a919d1deff 100644
--- a/lib/chef/provider/dsc_resource.rb
+++ b/lib/chef/provider/dsc_resource.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Edwards (<adamed@chef.io>)
#
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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.
@@ -15,25 +15,27 @@
# 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 "timeout" unless defined?(Timeout)
+require_relative "../mixin/powershell_exec"
+require_relative "../util/dsc/local_configuration_manager"
+require_relative "../mixin/powershell_type_coercions"
+require_relative "../util/dsc/resource_store"
class Chef
class Provider
class DscResource < Chef::Provider
include Chef::Mixin::PowershellTypeCoercions
- provides :dsc_resource, os: "windows"
+ provides :dsc_resource
def initialize(new_resource, run_context)
super
@new_resource = new_resource
@module_name = new_resource.module_name
+ @module_version = new_resource.module_version
@reboot_resource = nil
end
- def action_run
- if ! test_resource
+ action :run do
+ unless test_resource
converge_by(generate_description) do
result = set_resource
reboot_if_required
@@ -41,20 +43,15 @@ class Chef
end
end
- def load_current_resource
- end
-
- def whyrun_supported?
- true
- end
+ def load_current_resource; end
def define_resource_requirements
requirements.assert(:run) do |a|
a.assertion { supports_dsc_invoke_resource? }
- err = ["You must have Powershell version >= 5.0.10018.0 to use dsc_resource."]
+ err = ["You must have PowerShell version >= 5.0.10018.0 to use dsc_resource."]
a.failure_message Chef::Exceptions::ProviderNotFound,
err
- a.whyrun err + ["Assuming a previous resource installs Powershell 5.0.10018.0 or higher."]
+ a.whyrun err + ["Assuming a previous resource installs PowerShell 5.0.10018.0 or higher."]
a.block_action!
end
requirements.assert(:run) do |a|
@@ -65,6 +62,13 @@ class Chef
a.whyrun err + ["Assuming a previous resource sets the RefreshMode."]
a.block_action!
end
+ requirements.assert(:run) do |a|
+ a.assertion { module_usage_valid? }
+ err = ["module_name must be supplied along with module_version."]
+ a.failure_message Chef::Exceptions::DSCModuleNameMissing,
+ err
+ a.block_action!
+ end
end
protected
@@ -92,6 +96,10 @@ class Chef
Chef::Platform.supports_refresh_mode_enabled?(node)
end
+ def module_usage_valid?
+ !(!@module_name && @module_version)
+ end
+
def generate_description
@converge_description
end
@@ -123,71 +131,62 @@ class Chef
def test_resource
result = invoke_resource(:test)
add_dsc_verbose_log(result)
- return_dsc_resource_result(result, "InDesiredState")
+ result.result["InDesiredState"]
end
def set_resource
result = invoke_resource(:set)
add_dsc_verbose_log(result)
- create_reboot_resource if return_dsc_resource_result(result, "RebootRequired")
- result.return_value
+ create_reboot_resource if result.result["RebootRequired"]
+ result
end
def add_dsc_verbose_log(result)
# We really want this information from the verbose stream,
# however in some versions of WMF, Invoke-DscResource is not correctly
# writing to that stream and instead just dumping to stdout
- verbose_output = result.stream(:verbose)
- verbose_output = result.stdout if verbose_output.empty?
+ verbose_output = result.verbose.join("\n")
+ verbose_output = result.result if verbose_output.empty?
if @converge_description.nil? || @converge_description.empty?
@converge_description = verbose_output
else
- @converge_description << "\n"
+ @converge_description << "\n\n"
@converge_description << verbose_output
end
end
- def invoke_resource(method, output_format = :object)
- properties = translate_type(@new_resource.properties)
- switches = "-Method #{method} -Name #{@new_resource.resource}"\
- " -Property #{properties} -Module #{module_name} -Verbose"
- cmdlet = Chef::Util::Powershell::Cmdlet.new(
- node,
- "Invoke-DscResource #{switches}",
- output_format
- )
- cmdlet.run!({}, { :timeout => new_resource.timeout })
+ def module_info_object
+ @module_version.nil? ? module_name : "@{ModuleName='#{module_name}';ModuleVersion='#{@module_version}'}"
end
- 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
+ def invoke_resource(method)
+ properties = translate_type(new_resource.properties)
+ switches = "-Method #{method} -Name #{new_resource.resource}"\
+ " -Property #{properties} -Module #{module_info_object} -Verbose"
+ Timeout.timeout(new_resource.timeout) {
+ powershell_exec!("Invoke-DscResource #{switches}")
+ }
end
def create_reboot_resource
@reboot_resource = Chef::Resource::Reboot.new(
- "Reboot for #{@new_resource.name}",
+ "Reboot for #{new_resource.name}",
run_context
).tap do |r|
- r.reason("Reboot for #{@new_resource.resource}.")
+ r.reason("Reboot for #{new_resource.resource}.")
end
end
def reboot_if_required
- reboot_action = @new_resource.reboot_action
+ 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.")
+ logger.trace("A reboot was requested by the DSC resource, but reboot_action is :nothing.")
+ logger.trace("This dsc_resource will not reboot the node.")
else
- Chef::Log.debug("Requesting node reboot with #{reboot_action}.")
+ logger.trace("Requesting node reboot with #{reboot_action}.")
@reboot_resource.run_action(reboot_action)
end
end
diff --git a/lib/chef/provider/dsc_script.rb b/lib/chef/provider/dsc_script.rb
index 66783ceb0f..d55f060f94 100644
--- a/lib/chef/provider/dsc_script.rb
+++ b/lib/chef/provider/dsc_script.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Edwards (<adamed@chef.io>)
#
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,35 +16,34 @@
# 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_relative "../util/dsc/configuration_generator"
+require_relative "../util/dsc/local_configuration_manager"
+require_relative "../util/path_helper"
class Chef
class Provider
class DscScript < Chef::Provider
- provides :dsc_script, os: "windows"
+ provides :dsc_script
def initialize(dsc_resource, run_context)
super(dsc_resource, run_context)
@dsc_resource = dsc_resource
@resource_converged = false
@operations = {
- :set => Proc.new do |config_manager, document, shellout_flags|
- config_manager.set_configuration(document, shellout_flags)
+ set: Proc.new do |config_manager, document|
+ config_manager.set_configuration(document)
end,
- :test => Proc.new do |config_manager, document, shellout_flags|
- config_manager.test_configuration(document, shellout_flags)
+ test: Proc.new do |config_manager, document|
+ config_manager.test_configuration(document)
end }
end
- def action_run
- if ! @resource_converged
+ action :run do
+ unless @resource_converged
converge_by(generate_description) do
run_configuration(:set)
- Chef::Log.info("DSC resource configuration completed successfully")
+ logger.info("DSC resource configuration completed successfully")
end
end
end
@@ -58,20 +57,16 @@ class Chef
end
end
- def whyrun_supported?
- true
- end
-
def define_resource_requirements
requirements.assert(:run) do |a|
err = [
"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.",
+ "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::ProviderNotFound, err.join(" ")
- a.whyrun err + ["Assuming a previous resource installs Powershell 4.0 or higher."]
+ a.whyrun err + ["Assuming a previous resource installs PowerShell 4.0 or higher."]
a.block_action!
end
end
@@ -89,20 +84,23 @@ class Chef
config_manager = Chef::Util::DSC::LocalConfigurationManager.new(@run_context.node, config_directory)
- shellout_flags = {
- :cwd => @dsc_resource.cwd,
- :environment => @dsc_resource.environment,
- :timeout => @dsc_resource.timeout,
- }
+ cwd = @dsc_resource.cwd || Dir.pwd
+ original_env = ENV.to_hash
begin
- configuration_document = generate_configuration_document(config_directory, configuration_flags)
- @operations[operation].call(config_manager, configuration_document, shellout_flags)
+ ENV.update(@dsc_resource.environment) if @dsc_resource.environment
+ Dir.chdir(cwd) do
+ Timeout.timeout(@dsc_resource.timeout) do
+ configuration_document = generate_configuration_document(config_directory, configuration_flags)
+ @operations[operation].call(config_manager, configuration_document)
+ end
+ end
rescue Exception => e
- Chef::Log.error("DSC operation failed: #{e.message}")
+ logger.error("DSC operation failed: #{e.message}")
raise e
ensure
::FileUtils.rm_rf(config_directory)
+ ENV.replace(original_env)
end
end
@@ -116,20 +114,14 @@ class Chef
end
def generate_configuration_document(config_directory, configuration_flags)
- shellout_flags = {
- :cwd => @dsc_resource.cwd,
- :environment => @dsc_resource.environment,
- :timeout => @dsc_resource.timeout,
- }
-
generator = Chef::Util::DSC::ConfigurationGenerator.new(@run_context.node, config_directory)
if @dsc_resource.command
- generator.configuration_document_from_script_path(@dsc_resource.command, configuration_name, configuration_flags, shellout_flags)
+ generator.configuration_document_from_script_path(@dsc_resource.command, configuration_name, configuration_flags)
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)
+ logger.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)
end
end
@@ -165,7 +157,11 @@ class Chef
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 }
- "converge DSC resource #{resource.name} by #{cleaned_messages.find_all { |c| c != '' }.join("\n")}"
+ unless cleaned_messages.empty?
+ "converge DSC resource #{resource.name} by #{cleaned_messages.find_all { |c| c != "" }.join("\n")}"
+ else
+ "converge DSC resource #{resource.name}"
+ end
else
# This is needed because a dsc script can have resources that are both converged and not
"converge DSC resource #{resource.name} by doing nothing because it is already converged"
@@ -175,9 +171,9 @@ class Chef
def powershell_info_str
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."
+ 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
deleted file mode 100644
index 5b252dd344..0000000000
--- a/lib/chef/provider/env.rb
+++ /dev/null
@@ -1,169 +0,0 @@
-#
-# Author:: Doug MacEachern (<dougm@vmware.com>)
-# Copyright:: Copyright 2010-2016, VMware, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require "chef/provider"
-require "chef/mixin/command"
-require "chef/resource/env"
-
-class Chef
- class Provider
- class Env < Chef::Provider
- include Chef::Mixin::Command
- attr_accessor :key_exists
-
- provides :env, os: "!windows"
-
- def initialize(new_resource, run_context)
- super
- @key_exists = true
- end
-
- def load_current_resource
- @current_resource = Chef::Resource::Env.new(@new_resource.name)
- @current_resource.key_name(@new_resource.key_name)
-
- if env_key_exists(@new_resource.key_name)
- @current_resource.value(env_value(@new_resource.key_name))
- else
- @key_exists = false
- Chef::Log.debug("#{@new_resource} key does not exist")
- end
-
- @current_resource
- end
-
- def env_value(key_name)
- raise Chef::Exceptions::Env, "#{self} provider does not implement env_value!"
- end
-
- def env_key_exists(key_name)
- env_value(key_name) ? true : false
- end
-
- # Check to see if value needs any changes
- #
- # ==== Returns
- # <true>:: If a change is required
- # <false>:: If a change is not required
- def requires_modify_or_create?
- if @new_resource.delim
- #e.g. check for existing value within PATH
- new_values.inject(0) do |index, val|
- next_index = current_values.find_index val
- return true if next_index.nil? || next_index < index
- next_index
- end
- false
- else
- @new_resource.value != @current_resource.value
- end
- end
-
- alias_method :compare_value, :requires_modify_or_create?
-
- def action_create
- if @key_exists
- if requires_modify_or_create?
- modify_env
- Chef::Log.info("#{@new_resource} altered")
- @new_resource.updated_by_last_action(true)
- end
- else
- create_env
- Chef::Log.info("#{@new_resource} created")
- @new_resource.updated_by_last_action(true)
- end
- end
-
- #e.g. delete a PATH element
- #
- # ==== Returns
- # <true>:: If we handled the element case and caller should not delete the key
- # <false>:: Caller should delete the key, either no :delim was specific or value was empty
- # after we removed the element.
- def delete_element
- return false unless @new_resource.delim #no delim: delete the key
- needs_delete = new_values.any? { |v| current_values.include?(v) }
- if !needs_delete
- Chef::Log.debug("#{@new_resource} element '#{@new_resource.value}' does not exist")
- return true #do not delete the key
- else
- new_value =
- current_values.select do |item|
- 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
- end
- end
-
- def action_delete
- if @key_exists && !delete_element
- delete_env
- Chef::Log.info("#{@new_resource} deleted")
- @new_resource.updated_by_last_action(true)
- end
- end
-
- def action_modify
- if @key_exists
- if requires_modify_or_create?
- modify_env
- Chef::Log.info("#{@new_resource} modified")
- @new_resource.updated_by_last_action(true)
- end
- else
- raise Chef::Exceptions::Env, "Cannot modify #{@new_resource} - key does not exist!"
- end
- end
-
- def create_env
- raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :#{@new_resource.action}"
- end
-
- def delete_env
- raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :delete"
- end
-
- def modify_env
- if @new_resource.delim
- @new_resource.value((new_values + current_values).uniq.join(@new_resource.delim))
- end
- create_env
- end
-
- # Returns the current values to split by delimiter
- def current_values
- @current_values ||= @current_resource.value.split(@new_resource.delim)
- end
-
- # Returns the new values to split by delimiter
- def new_values
- @new_values ||= @new_resource.value.split(@new_resource.delim)
- end
- end
- end
-end
diff --git a/lib/chef/provider/env/windows.rb b/lib/chef/provider/env/windows.rb
deleted file mode 100644
index a68c8276e0..0000000000
--- a/lib/chef/provider/env/windows.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-#
-# Author:: Doug MacEachern (<dougm@vmware.com>)
-# Copyright:: Copyright 2010-2016, VMware, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require "chef/mixin/windows_env_helper"
-
-class Chef
- class Provider
- class Env
- class Windows < Chef::Provider::Env
- include Chef::Mixin::WindowsEnvHelper
-
- provides :env, os: "windows"
-
- def create_env
- obj = env_obj(@new_resource.key_name)
- unless obj
- obj = WIN32OLE.connect("winmgmts://").get("Win32_Environment").spawninstance_
- obj.name = @new_resource.key_name
- obj.username = "<System>"
- end
- obj.variablevalue = @new_resource.value
- obj.put_
- value = @new_resource.value
- value = expand_path(value) if @new_resource.key_name.casecmp("PATH").zero?
- ENV[@new_resource.key_name] = value
- broadcast_env_change
- end
-
- def delete_env
- obj = env_obj(@new_resource.key_name)
- if obj
- obj.delete_
- broadcast_env_change
- end
- if ENV[@new_resource.key_name]
- ENV.delete(@new_resource.key_name)
- end
- end
-
- def env_value(key_name)
- obj = env_obj(key_name)
- return obj ? obj.variablevalue : ENV[key_name]
- end
-
- def env_obj(key_name)
- wmi = WmiLite::Wmi.new
- # Note that by design this query is case insensitive with regard to key_name
- environment_variables = wmi.query("select * from Win32_Environment where name = '#{key_name}'")
- if environment_variables && environment_variables.length > 0
- environment_variables[0].wmi_ole_object
- end
- end
-
- end
- end
- end
-end
diff --git a/lib/chef/provider/erl_call.rb b/lib/chef/provider/erl_call.rb
deleted file mode 100644
index 7167f3b8a5..0000000000
--- a/lib/chef/provider/erl_call.rb
+++ /dev/null
@@ -1,108 +0,0 @@
-#
-# Author:: Joe Williams (<joe@joetify.com>)
-# Copyright:: Copyright 2009-2016, 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/log"
-require "chef/mixin/command"
-require "chef/provider"
-
-class Chef
- class Provider
- class ErlCall < Chef::Provider
- include Chef::Mixin::Command
-
- provides :erl_call
-
- def initialize(node, new_resource)
- super(node, new_resource)
- end
-
- def whyrun_supported?
- true
- end
-
- def load_current_resource
- true
- end
-
- def action_run
- case @new_resource.name_type
- when "sname"
- node = "-sname #{@new_resource.node_name}"
- when "name"
- node = "-name #{@new_resource.node_name}"
- end
-
- if @new_resource.cookie
- cookie = "-c #{@new_resource.cookie}"
- else
- cookie = ""
- end
-
- if @new_resource.distributed
- distributed = "-s"
- else
- distributed = ""
- end
-
- command = "erl_call -e #{distributed} #{node} #{cookie}"
-
- converge_by("run erlang block") do
- begin
- pid, stdin, stdout, stderr = popen4(command, :waitlast => true)
-
- Chef::Log.debug("#{@new_resource} running")
- Chef::Log.debug("#{@new_resource} command: #{command}")
- Chef::Log.debug("#{@new_resource} code: #{@new_resource.code}")
-
- @new_resource.code.each_line { |line| stdin.puts(line.chomp) }
-
- stdin.close
-
- Chef::Log.debug("#{@new_resource} output: ")
-
- stdout_output = ""
- stdout.each_line { |line| stdout_output << line }
- stdout.close
-
- stderr_output = ""
- stderr.each_line { |line| stderr_output << line }
- stderr.close
-
- # fail if stderr contains anything
- if stderr_output.length > 0
- raise Chef::Exceptions::ErlCall, stderr_output
- end
-
- # fail if the first 4 characters aren't "{ok,"
- unless stdout_output[0..3].include?("{ok,")
- raise Chef::Exceptions::ErlCall, stdout_output
- end
-
- @new_resource.updated_by_last_action(true)
-
- Chef::Log.debug("#{@new_resource} #{stdout_output}")
- Chef::Log.info("#{@new_resouce} ran successfully")
- ensure
- Process.wait(pid) if pid
- end
- end
- end
-
- end
- end
-end
diff --git a/lib/chef/provider/execute.rb b/lib/chef/provider/execute.rb
index 45f0ad5488..20b8a40bf1 100644
--- a/lib/chef/provider/execute.rb
+++ b/lib/chef/provider/execute.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,59 +16,53 @@
# limitations under the License.
#
-require "chef/log"
-require "chef/provider"
-require "forwardable"
+require_relative "../log"
+require_relative "../provider"
+require "forwardable" unless defined?(Forwardable)
class Chef
class Provider
class Execute < Chef::Provider
extend Forwardable
- provides :execute
+ provides :execute, target_mode: true
- def_delegators :@new_resource, :command, :returns, :environment, :user, :group, :cwd, :umask, :creates
+ def_delegators :new_resource, :command, :returns, :environment, :user, :domain, :password, :group, :cwd, :umask, :creates, :elevated, :default_env, :timeout, :input
def load_current_resource
current_resource = Chef::Resource::Execute.new(new_resource.name)
current_resource
end
- def whyrun_supported?
- true
- end
-
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 in the future (CHEF-3819)"
+ # FIXME? move this onto the resource?
+ raise Chef::Exceptions::Execute, "Please either specify a full path for the creates property, or specify a cwd property to the #{new_resource} resource"
end
end
- def timeout
- # original implementation did not specify a timeout, but ShellOut
- # *always* times out. So, set a very long default timeout
- new_resource.timeout || 3600
- end
-
- def action_run
+ action :run do
if creates && sentinel_file.exist?
- Chef::Log.debug("#{new_resource} sentinel file #{sentinel_file} exists - nothing to do")
+ logger.debug("#{new_resource} sentinel file #{sentinel_file} exists - nothing to do")
return false
end
converge_by("execute #{description}") do
begin
- shell_out!(command, opts)
+ shell_out!(command, **opts)
rescue Mixlib::ShellOut::ShellCommandFailed
if sensitive?
- raise Mixlib::ShellOut::ShellCommandFailed,
- "Command execution failed. STDOUT/STDERR suppressed for sensitive resource"
+ ex = Mixlib::ShellOut::ShellCommandFailed.new("Command execution failed. STDOUT/STDERR suppressed for sensitive resource")
+ # Forcibly hide the exception cause chain here so we don't log the unredacted version
+ def ex.cause
+ nil
+ end
+ raise ex
else
raise
end
end
- Chef::Log.info("#{new_resource} ran successfully")
+ logger.info("#{new_resource} ran successfully")
end
end
@@ -92,18 +86,23 @@ class Chef
opts[:returns] = returns if returns
opts[:environment] = environment if environment
opts[:user] = user if user
+ opts[:domain] = domain if domain
+ opts[:password] = password if password
opts[:group] = group if group
opts[:cwd] = cwd if cwd
opts[:umask] = umask if umask
+ opts[:input] = input if input
+ opts[:default_env] = default_env
opts[:log_level] = :info
opts[:log_tag] = new_resource.to_s
- if (Chef::Log.info? || live_stream?) && !sensitive?
+ if (logger.info? || live_stream?) && !sensitive?
if run_context.events.formatter?
- opts[:live_stream] = Chef::EventDispatch::EventsOutputStream.new(run_context.events, :name => :execute)
+ opts[:live_stream] = Chef::EventDispatch::EventsOutputStream.new(run_context.events, name: :execute)
elsif stream_to_stdout?
opts[:live_stream] = STDOUT
end
end
+ opts[:elevated] = elevated if elevated
opts
end
@@ -117,9 +116,10 @@ class Chef
def sentinel_file
Pathname.new(Chef::Util::PathHelper.cleanpath(
- ( cwd && creates_relative? ) ? ::File.join(cwd, creates) : creates
+ ( cwd && creates_relative? ) ? ::File.join(cwd, creates) : creates
))
end
+
end
end
end
diff --git a/lib/chef/provider/file.rb b/lib/chef/provider/file.rb
index 7f85085eeb..e2c07ad9f7 100644
--- a/lib/chef/provider/file.rb
+++ b/lib/chef/provider/file.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,22 +17,21 @@
# 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/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"
+require_relative "../config"
+require_relative "../log"
+require_relative "../resource/file"
+require_relative "../provider"
+require "etc" unless defined?(Etc)
+require "fileutils" unless defined?(FileUtils)
+require_relative "../scan_access_control"
+require_relative "../mixin/checksum"
+require_relative "../mixin/file_class"
+require_relative "../mixin/enforce_ownership_and_permissions"
+require_relative "../util/backup"
+require_relative "../util/diff"
+require_relative "../util/selinux"
+require_relative "../file_content_management/deploy"
+require "chef-utils" unless defined?(ChefUtils::CANARY)
# The Tao of File Providers:
# - the content provider must always return a tempfile that we can delete/mv
@@ -52,10 +51,6 @@ class Chef
include Chef::Util::Selinux
include Chef::Mixin::FileClass
- extend Chef::Deprecation::Warnings
- include Chef::Deprecation::Provider::File
- add_deprecation_warnings_for(Chef::Deprecation::Provider::File.instance_methods)
-
provides :file
attr_reader :deployment_strategy
@@ -72,10 +67,6 @@ class Chef
super
end
- def whyrun_supported?
- true
- end
-
def load_current_resource
# true if there is a symlink and we need to manage what it points at
@managing_symlink = file_class.symlink?(new_resource.path) && ( new_resource.manage_symlink_source || new_resource.manage_symlink_source.nil? )
@@ -93,16 +84,16 @@ class Chef
end
# true if we are going to be creating a new file
- @needs_creating = !::File.exist?(new_resource.path) || needs_unlinking?
+ @needs_creating = !::File.exist?(new_resource.path) || needs_unlinking?
- # Let children resources override constructing the @current_resource
+ # Let children resources override constructing the current_resource
@current_resource ||= Chef::Resource::File.new(new_resource.name)
current_resource.path(new_resource.path)
- if !needs_creating?
+ unless needs_creating?
# we are updating an existing file
if managing_content?
- Chef::Log.debug("#{new_resource} checksumming file at #{new_resource.path}.")
+ logger.trace("#{new_resource} checksumming file at #{new_resource.path}.")
current_resource.checksum(checksum(current_resource.path))
else
# if the file does not exist or is not a file, then the checksum is invalid/pointless
@@ -120,17 +111,17 @@ class Chef
# Make sure the parent directory exists, otherwise fail. For why-run assume it would have been created.
requirements.assert(:create, :create_if_missing, :touch) do |a|
- parent_directory = ::File.dirname(@new_resource.path)
+ parent_directory = ::File.dirname(new_resource.path)
a.assertion { ::File.directory?(parent_directory) }
a.failure_message(Chef::Exceptions::EnclosingDirectoryDoesNotExist, "Parent directory #{parent_directory} does not exist.")
a.whyrun("Assuming directory #{parent_directory} would have been created")
end
# Make sure the file is deletable if it exists, otherwise fail.
- if ::File.exist?(@new_resource.path)
+ if ::File.exist?(new_resource.path)
requirements.assert(:delete) do |a|
- a.assertion { ::File.writable?(@new_resource.path) }
- a.failure_message(Chef::Exceptions::InsufficientPermissions, "File #{@new_resource.path} exists but is not writable so it cannot be deleted")
+ a.assertion { ::File.writable?(new_resource.path) }
+ a.failure_message(Chef::Exceptions::InsufficientPermissions, "File #{new_resource.path} exists but is not writable so it cannot be deleted")
end
end
@@ -146,7 +137,7 @@ class Chef
end
end
- def action_create
+ action :create do
do_generate_content
do_validate_content
do_unlink
@@ -154,33 +145,33 @@ class Chef
do_contents_changes
do_acl_changes
do_selinux
- load_resource_attributes_from_file(@new_resource)
+ load_resource_attributes_from_file(new_resource) unless Chef::Config[:why_run]
end
- def action_create_if_missing
- unless ::File.exist?(@new_resource.path)
+ action :create_if_missing do
+ unless ::File.exist?(new_resource.path)
action_create
else
- Chef::Log.debug("#{@new_resource} exists at #{@new_resource.path} taking no action.")
+ logger.trace("#{new_resource} exists at #{new_resource.path} taking no action.")
end
end
- def action_delete
- if ::File.exists?(@new_resource.path)
- converge_by("delete file #{@new_resource.path}") do
- do_backup unless file_class.symlink?(@new_resource.path)
- ::File.delete(@new_resource.path)
- Chef::Log.info("#{@new_resource} deleted file at #{@new_resource.path}")
+ action :delete do
+ if ::File.exists?(new_resource.path)
+ converge_by("delete file #{new_resource.path}") do
+ do_backup unless file_class.symlink?(new_resource.path)
+ ::File.delete(new_resource.path)
+ logger.info("#{new_resource} deleted file at #{new_resource.path}")
end
end
end
- def action_touch
+ action :touch do
action_create
- converge_by("update utime on file #{@new_resource.path}") do
+ converge_by("update utime on file #{new_resource.path}") do
time = Time.now
- ::File.utime(time, time, @new_resource.path)
- Chef::Log.info("#{@new_resource} updated atime and mtime to #{time}")
+ ::File.utime(time, time, new_resource.path)
+ logger.info("#{new_resource} updated atime and mtime to #{time}")
end
end
@@ -197,8 +188,9 @@ class Chef
# content (for things like doing checksums in load_current_resource). Expected to
# be overridden in subclasses.
def managing_content?
- return true if @new_resource.checksum
- return true if !@new_resource.content.nil? && @action != :create_if_missing
+ return true if new_resource.checksum
+ return true if !new_resource.content.nil? && @action != :create_if_missing
+
false
end
@@ -228,25 +220,25 @@ class Chef
# assertions, which then decide whether or not to raise or issue a
# warning for whyrun mode.
def inspect_existing_fs_entry
- path = @new_resource.path
+ path = new_resource.path
if !l_exist?(path)
[nil, nil, nil]
elsif real_file?(path)
[nil, nil, nil]
- elsif file_class.symlink?(path) && @new_resource.manage_symlink_source
+ elsif file_class.symlink?(path) && new_resource.manage_symlink_source
verify_symlink_sanity(path)
- elsif file_class.symlink?(@new_resource.path) && @new_resource.manage_symlink_source.nil?
- Chef::Log.warn("File #{path} managed by #{@new_resource} is really a symlink. Managing the source file instead.")
- Chef::Log.warn("Disable this warning by setting `manage_symlink_source true` on the resource")
- Chef::Log.warn("In a future Chef release, 'manage_symlink_source' will not be enabled by default")
+ elsif file_class.symlink?(new_resource.path) && new_resource.manage_symlink_source.nil?
+ logger.warn("File #{path} managed by #{new_resource} is really a symlink (to #{file_class.realpath(new_resource.path)}). Managing the source file instead.")
+ logger.warn("Disable this warning by setting `manage_symlink_source true` on the resource")
+ logger.warn("In a future release, 'manage_symlink_source' will not be enabled by default")
verify_symlink_sanity(path)
- elsif @new_resource.force_unlink
+ elsif new_resource.force_unlink
[nil, nil, nil]
else
[ Chef::Exceptions::FileTypeMismatch,
- "File #{path} exists, but is a #{file_type_string(@new_resource.path)}, set force_unlink to true to remove",
- "Assuming #{file_type_string(@new_resource.path)} at #{@new_resource.path} would have been removed by a previous resource",
+ "File #{path} exists, but is a #{file_type_string(new_resource.path)}, set force_unlink to true to remove",
+ "Assuming #{file_type_string(new_resource.path)} at #{new_resource.path} would have been removed by a previous resource",
]
end
end
@@ -282,8 +274,8 @@ class Chef
def content
@content ||= begin
- load_current_resource if @current_resource.nil?
- @content_class.new(@new_resource, @current_resource, @run_context)
+ load_current_resource if current_resource.nil?
+ @content_class.new(new_resource, current_resource, @run_context, logger)
end
end
@@ -312,11 +304,9 @@ class Chef
# like real_file? that follows (sane) symlinks
def symlink_to_real_file?(path)
- begin
- real_file?(::File.realpath(path))
- rescue Errno::ELOOP, Errno::ENOENT
- false
- end
+ real_file?(::File.realpath(path))
+ rescue Errno::ELOOP, Errno::ENOENT
+ false
end
# Similar to File.exist?, but also returns true in the case that the
@@ -350,20 +340,23 @@ class Chef
if tempfile
new_resource.verify.each do |v|
- if ! v.verify(tempfile.path)
- raise Chef::Exceptions::ValidationFailed.new "Proposed content for #{new_resource.path} failed verification #{v}"
+ unless v.verify(tempfile.path)
+ backupfile = "#{Chef::Config[:file_cache_path]}/failed_validations/#{::File.basename(tempfile.path)}"
+ FileUtils.mkdir_p ::File.dirname(backupfile)
+ FileUtils.cp tempfile.path, backupfile
+ raise Chef::Exceptions::ValidationFailed.new "Proposed content for #{new_resource.path} failed verification #{new_resource.sensitive ? "[sensitive]" : "#{v}\n#{v.output}"}\nTemporary file moved to #{backupfile}"
end
end
end
end
def do_unlink
- if @new_resource.force_unlink
+ if new_resource.force_unlink
if needs_unlinking?
# unlink things that aren't normal files
- description = "unlink #{file_type_string(@new_resource.path)} at #{@new_resource.path}"
+ description = "unlink #{file_type_string(new_resource.path)} at #{new_resource.path}"
converge_by(description) do
- unlink(@new_resource.path)
+ unlink(new_resource.path)
end
end
end
@@ -371,15 +364,15 @@ class Chef
def do_create_file
if needs_creating?
- converge_by("create new file #{@new_resource.path}") do
- deployment_strategy.create(@new_resource.path)
- Chef::Log.info("#{@new_resource} created file #{@new_resource.path}")
+ converge_by("create new file #{new_resource.path}") do
+ deployment_strategy.create(new_resource.path)
+ logger.info("#{new_resource} created file #{new_resource.path}")
end
end
end
def do_backup(file = nil)
- Chef::Util::Backup.new(@new_resource, file).backup!
+ Chef::Util::Backup.new(new_resource, file).backup!
end
def diff
@@ -388,8 +381,8 @@ 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).force_encoding(Chef::Config[:ruby_encoding]))
+ logger.info("#{new_resource} updated file contents #{new_resource.path}")
if managing_content?
# save final checksum for reporting.
new_resource.final_checksum = checksum(new_resource.path)
@@ -401,21 +394,21 @@ class Chef
return if tempfile.nil?
# but a tempfile that has no path or doesn't exist should not happen
if tempfile.path.nil? || !::File.exists?(tempfile.path)
- raise "chef-client is confused, trying to deploy a file that has no path or does not exist..."
+ raise "#{ChefUtils::Dist::Infra::CLIENT} is confused, trying to deploy a file that has no path or does not exist..."
end
# the file? on the next line suppresses the case in why-run when we have a not-file here that would have otherwise been removed
- if ::File.file?(@new_resource.path) && contents_changed?
- description = [ "update content in file #{@new_resource.path} from \
-#{short_cksum(@current_resource.checksum)} to #{short_cksum(tempfile_checksum)}" ]
+ if ::File.file?(new_resource.path) && contents_changed?
+ description = [ "update content in file #{new_resource.path} from \
+#{short_cksum(current_resource.checksum)} to #{short_cksum(tempfile_checksum)}" ]
# Hide the diff output if the resource is marked as a sensitive resource
- if @new_resource.sensitive
- @new_resource.diff("suppressed sensitive resource")
+ if new_resource.sensitive
+ new_resource.diff("suppressed sensitive resource")
description << "suppressed sensitive resource"
else
- diff.diff(@current_resource.path, tempfile.path)
- @new_resource.diff( diff.for_reporting ) unless needs_creating?
+ diff.diff(current_resource.path, tempfile.path)
+ new_resource.diff( diff.for_reporting ) unless needs_creating?
description << diff.for_output
end
@@ -437,10 +430,10 @@ class Chef
if resource_updated? && Chef::Config[:enable_selinux_file_permission_fixup]
if selinux_enabled?
converge_by("restore selinux security context") do
- restore_security_context(::File.realpath(@new_resource.path), recursive)
+ restore_security_context(::File.realpath(new_resource.path), recursive)
end
else
- Chef::Log.debug "selinux utilities can not be found. Skipping selinux permission fixup."
+ logger.trace "selinux utilities can not be found. Skipping selinux permission fixup."
end
end
end
@@ -454,28 +447,24 @@ class Chef
end
def contents_changed?
- Chef::Log.debug "calculating checksum of #{tempfile.path} to compare with #{@current_resource.checksum}"
- tempfile_checksum != @current_resource.checksum
+ logger.trace "calculating checksum of #{tempfile.path} to compare with #{current_resource.checksum}"
+ tempfile_checksum != current_resource.checksum
end
def tempfile
@tempfile ||= content.tempfile
end
- def short_cksum(checksum)
- return "none" if checksum.nil?
- checksum.slice(0, 6)
- end
-
def load_resource_attributes_from_file(resource)
- if Chef::Platform.windows?
+ if ChefUtils.windows?
# This is a work around for CHEF-3554.
# OC-6534: is tracking the real fix for this workaround.
# Add support for Windows equivalent, or implicit resource
# reporting won't work for Windows.
return
end
- acl_scanner = ScanAccessControl.new(@new_resource, resource)
+
+ acl_scanner = ScanAccessControl.new(new_resource, resource)
acl_scanner.set_all!
end
diff --git a/lib/chef/provider/file/content.rb b/lib/chef/provider/file/content.rb
index 1b60e10fea..30ca38d3b6 100644
--- a/lib/chef/provider/file/content.rb
+++ b/lib/chef/provider/file/content.rb
@@ -1,6 +1,6 @@
#
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/file_content_management/content_base"
-require "chef/file_content_management/tempfile"
+require_relative "../../file_content_management/content_base"
+require_relative "../../file_content_management/tempfile"
class Chef
class Provider
diff --git a/lib/chef/provider/git.rb b/lib/chef/provider/git.rb
index d051bb1d92..c0f6f01c59 100644
--- a/lib/chef/provider/git.rb
+++ b/lib/chef/provider/git.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@kallistec.com>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,10 +16,10 @@
# limitations under the License.
#
-require "chef/exceptions"
-require "chef/log"
-require "chef/provider"
-require "fileutils"
+require_relative "../exceptions"
+require_relative "../log"
+require_relative "../provider"
+require "fileutils" unless defined?(FileUtils)
class Chef
class Provider
@@ -28,78 +28,90 @@ class Chef
extend Forwardable
provides :git
- def_delegator :@new_resource, :destination, :cwd
+ GIT_VERSION_PATTERN = Regexp.compile('git version (\d+\.\d+.\d+)')
- def whyrun_supported?
- true
- end
+ def_delegator :new_resource, :destination, :cwd
def load_current_resource
@resolved_reference = nil
- @current_resource = Chef::Resource::Git.new(@new_resource.name)
+ @current_resource = Chef::Resource::Git.new(new_resource.name)
if current_revision = find_current_revision
- @current_resource.revision current_revision
+ current_resource.revision current_revision
end
end
def define_resource_requirements
+ unless new_resource.user.nil?
+ requirements.assert(:all_actions) do |a|
+ a.assertion do
+
+ get_homedir(new_resource.user)
+ rescue ArgumentError
+ false
+
+ end
+ a.whyrun("User #{new_resource.user} does not exist, this run will fail unless it has been previously created. Assuming it would have been created.")
+ a.failure_message(Chef::Exceptions::User, "#{new_resource.user} required by resource #{new_resource.name} does not exist")
+ end
+ end
+
# Parent directory of the target must exist.
requirements.assert(:checkout, :sync) do |a|
dirname = ::File.dirname(cwd)
a.assertion { ::File.directory?(dirname) }
a.whyrun("Directory #{dirname} does not exist, this run will fail unless it has been previously created. Assuming it would have been created.")
a.failure_message(Chef::Exceptions::MissingParentDirectory,
- "Cannot clone #{@new_resource} to #{cwd}, the enclosing directory #{dirname} does not exist")
+ "Cannot clone #{new_resource} to #{cwd}, the enclosing directory #{dirname} does not exist")
end
requirements.assert(:all_actions) do |a|
- a.assertion { !(@new_resource.revision =~ /^origin\//) }
+ a.assertion { !(new_resource.revision =~ %r{^origin/}) }
a.failure_message Chef::Exceptions::InvalidRemoteGitReference,
- "Deploying remote branches is not supported. " +
- "Specify the remote branch as a local branch for " +
- "the git repository you're deploying from " +
- "(ie: '#{@new_resource.revision.gsub('origin/', '')}' rather than '#{@new_resource.revision}')."
+ "Deploying remote branches is not supported. " +
+ "Specify the remote branch as a local branch for " +
+ "the git repository you're deploying from " +
+ "(ie: '#{new_resource.revision.gsub("origin/", "")}' rather than '#{new_resource.revision}')."
end
requirements.assert(:all_actions) do |a|
# this can't be recovered from in why-run mode, because nothing that
# we do in the course of a run is likely to create a valid target_revision
# if we can't resolve it up front.
- a.assertion { target_revision != nil }
+ a.assertion { !target_revision.nil? }
a.failure_message Chef::Exceptions::UnresolvableGitReference,
- "Unable to parse SHA reference for '#{@new_resource.revision}' in repository '#{@new_resource.repository}'. " +
- "Verify your (case-sensitive) repository URL and revision.\n" +
- "`git ls-remote '#{@new_resource.repository}' '#{rev_search_pattern}'` output: #{@resolved_reference}"
+ "Unable to parse SHA reference for '#{new_resource.revision}' in repository '#{new_resource.repository}'. " +
+ "Verify your (case-sensitive) repository URL and revision.\n" +
+ "`git ls-remote '#{new_resource.repository}' '#{rev_search_pattern}'` output: #{@resolved_reference}"
end
end
- def action_checkout
+ action :checkout do
if target_dir_non_existent_or_empty?
clone
- if @new_resource.enable_checkout
+ if new_resource.enable_checkout
checkout
end
enable_submodules
add_remotes
else
- Chef::Log.debug "#{@new_resource} checkout destination #{cwd} already exists or is a non-empty directory"
+ logger.trace "#{new_resource} checkout destination #{cwd} already exists or is a non-empty directory"
end
end
- def action_export
+ action :export do
action_checkout
converge_by("complete the export by removing #{cwd}.git after checkout") do
FileUtils.rm_rf(::File.join(cwd, ".git"))
end
end
- def action_sync
+ action :sync do
if existing_git_clone?
- Chef::Log.debug "#{@new_resource} current revision: #{@current_resource.revision} target revision: #{target_revision}"
+ logger.trace "#{new_resource} current revision: #{current_resource.revision} target revision: #{target_revision}"
unless current_revision_matches_target_revision?
fetch_updates
enable_submodules
- Chef::Log.info "#{@new_resource} updated to revision #{target_revision}"
+ logger.info "#{new_resource} updated to revision #{target_revision}"
end
add_remotes
else
@@ -107,8 +119,22 @@ class Chef
end
end
- def git_minor_version
- @git_minor_version ||= Gem::Version.new( git("--version").stdout.split.last )
+ def git_has_single_branch_option?
+ @git_has_single_branch_option ||= !git_gem_version.nil? && git_gem_version >= Gem::Version.new("1.7.10")
+ end
+
+ def git_gem_version
+ return @git_gem_version if defined?(@git_gem_version)
+
+ output = git("--version").stdout
+ match = GIT_VERSION_PATTERN.match(output)
+ if match
+ @git_gem_version = Gem::Version.new(match[1])
+ else
+ logger.warn "Unable to parse git version from '#{output}'"
+ @git_gem_version = nil
+ end
+ @git_gem_version
end
def existing_git_clone?
@@ -120,7 +146,7 @@ class Chef
end
def find_current_revision
- Chef::Log.debug("#{@new_resource} finding current git revision")
+ logger.trace("#{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 = git("rev-parse", "HEAD", cwd: cwd, returns: [0, 128]).stdout.strip
@@ -128,11 +154,16 @@ class Chef
sha_hash?(result) ? result : nil
end
+ def already_on_target_branch?
+ current_branch = git("rev-parse", "--abbrev-ref", "HEAD", cwd: cwd, returns: [0, 128]).stdout.strip
+ current_branch == (new_resource.checkout_branch || new_resource.revision)
+ end
+
def add_remotes
- if @new_resource.additional_remotes.length > 0
- @new_resource.additional_remotes.each_pair do |remote_name, remote_url|
+ if new_resource.additional_remotes.length > 0
+ new_resource.additional_remotes.each_pair do |remote_name, remote_url|
converge_by("add remote #{remote_name} from #{remote_url}") do
- Chef::Log.info "#{@new_resource} adding git remote #{remote_name} = #{remote_url}"
+ logger.info "#{new_resource} adding git remote #{remote_name} = #{remote_url}"
setup_remote_tracking_branches(remote_name, remote_url)
end
end
@@ -140,38 +171,52 @@ class Chef
end
def clone
- converge_by("clone from #{@new_resource.repository} into #{cwd}") do
- remote = @new_resource.remote
+ converge_by("clone from #{repo_url} into #{cwd}") do
+ remote = new_resource.remote
clone_cmd = ["clone"]
clone_cmd << "-o #{remote}" unless remote == "origin"
- clone_cmd << "--depth #{@new_resource.depth}" if @new_resource.depth
- clone_cmd << "--no-single-branch" if @new_resource.depth && git_minor_version >= Gem::Version.new("1.7.10")
- clone_cmd << "\"#{@new_resource.repository}\""
+ clone_cmd << "--depth #{new_resource.depth}" if new_resource.depth
+ clone_cmd << "--no-single-branch" if new_resource.depth && git_has_single_branch_option?
+ clone_cmd << "\"#{new_resource.repository}\""
clone_cmd << "\"#{cwd}\""
- Chef::Log.info "#{@new_resource} cloning repo #{@new_resource.repository} to #{cwd}"
+ logger.info "#{new_resource} cloning repo #{repo_url} to #{cwd}"
git clone_cmd
end
end
def checkout
- sha_ref = target_revision
-
- converge_by("checkout ref #{sha_ref} branch #{@new_resource.revision}") do
+ converge_by("checkout ref #{target_revision} branch #{new_resource.revision}") do
# checkout into a local branch rather than a detached HEAD
- git("branch", "-f", @new_resource.checkout_branch, sha_ref, cwd: cwd)
- git("checkout", @new_resource.checkout_branch, cwd: cwd)
- Chef::Log.info "#{@new_resource} checked out branch: #{@new_resource.revision} onto: #{@new_resource.checkout_branch} reference: #{sha_ref}"
+ if new_resource.checkout_branch
+ # check out to a local branch
+ git("branch", "-f", new_resource.checkout_branch, target_revision, cwd: cwd)
+ git("checkout", new_resource.checkout_branch, cwd: cwd)
+ logger.info "#{new_resource} checked out branch: #{new_resource.revision} onto: #{new_resource.checkout_branch} reference: #{target_revision}"
+ elsif sha_hash?(new_resource.revision) || !is_branch?
+ # detached head
+ git("checkout", target_revision, cwd: cwd)
+ logger.info "#{new_resource} checked out reference: #{target_revision}"
+ elsif already_on_target_branch?
+ # we are already on the proper branch
+ git("reset", "--hard", target_revision, cwd: cwd)
+ else
+ # need a branch with a tracking branch
+ git("branch", "-f", new_resource.revision, target_revision, cwd: cwd)
+ git("checkout", new_resource.revision, cwd: cwd)
+ git("branch", "-u", "#{new_resource.remote}/#{new_resource.revision}", cwd: cwd)
+ logger.info "#{new_resource} checked out branch: #{new_resource.revision} reference: #{target_revision}"
+ end
end
end
def enable_submodules
- if @new_resource.enable_submodules
- converge_by("enable git submodules for #{@new_resource}") do
- Chef::Log.info "#{@new_resource} synchronizing git submodules"
+ if new_resource.enable_submodules
+ converge_by("enable git submodules for #{new_resource}") do
+ logger.info "#{new_resource} synchronizing git submodules"
git("submodule", "sync", cwd: cwd)
- Chef::Log.info "#{@new_resource} enabling git submodules"
+ logger.info "#{new_resource} enabling git submodules"
# the --recursive flag means we require git 1.6.5+ now, see CHEF-1827
git("submodule", "update", "--init", "--recursive", cwd: cwd)
end
@@ -179,19 +224,31 @@ class Chef
end
def fetch_updates
- setup_remote_tracking_branches(@new_resource.remote, @new_resource.repository)
- converge_by("fetch updates for #{@new_resource.remote}") do
+ setup_remote_tracking_branches(new_resource.remote, new_resource.repository)
+ converge_by("fetch updates for #{new_resource.remote}") do
# since we're in a local branch already, just reset to specified revision rather than merge
- Chef::Log.debug "Fetching updates from #{new_resource.remote} and resetting to revision #{target_revision}"
- git("fetch", @new_resource.remote, cwd: cwd)
- git("fetch", @new_resource.remote, "--tags", cwd: cwd)
- git("reset", "--hard", target_revision, cwd: cwd)
+ logger.trace "Fetching updates from #{new_resource.remote} and resetting to revision #{target_revision}"
+ git("fetch", "--prune", new_resource.remote, cwd: cwd)
+ git("fetch", new_resource.remote, "--tags", cwd: cwd)
+ if sha_hash?(new_resource.revision) || is_tag? || already_on_target_branch?
+ # detached head or if we are already on the proper branch
+ git("reset", "--hard", target_revision, cwd: cwd)
+ elsif new_resource.checkout_branch
+ # check out to a local branch
+ git("branch", "-f", new_resource.checkout_branch, target_revision, cwd: cwd)
+ git("checkout", new_resource.checkout_branch, cwd: cwd)
+ else
+ # need a branch with a tracking branch
+ git("branch", "-f", new_resource.revision, target_revision, cwd: cwd)
+ git("checkout", new_resource.revision, cwd: cwd)
+ git("branch", "-u", "#{new_resource.remote}/#{new_resource.revision}", cwd: cwd)
+ end
end
end
def setup_remote_tracking_branches(remote_name, remote_url)
converge_by("set up remote tracking branches for #{remote_url} at #{remote_name}") do
- Chef::Log.debug "#{@new_resource} configuring remote tracking branches for repository #{remote_url} " + "at remote #{remote_name}"
+ logger.trace "#{new_resource} configuring remote tracking branches for repository #{remote_url} " + "at remote #{remote_name}"
check_remote_command = ["config", "--get", "remote.#{remote_name}.url"]
remote_status = git(check_remote_command, cwd: cwd, returns: [0, 1, 2])
case remote_status.exitstatus
@@ -202,7 +259,7 @@ class Chef
# which we can fix by replacing them all with our target url (hence the --replace-all option)
if multiple_remotes?(remote_status) || !remote_matches?(remote_url, remote_status)
- git("config", "--replace-all", "remote.#{remote_name}.url", remote_url, cwd: cwd)
+ git("config", "--replace-all", "remote.#{remote_name}.url", %{"#{remote_url}"}, cwd: cwd)
end
when 1
git("remote", "add", remote_name, remote_url, cwd: cwd)
@@ -219,23 +276,24 @@ class Chef
end
def current_revision_matches_target_revision?
- (!@current_resource.revision.nil?) && (target_revision.strip.to_i(16) == @current_resource.revision.strip.to_i(16))
+ (!current_resource.revision.nil?) && (target_revision.strip.to_i(16) == current_resource.revision.strip.to_i(16))
end
def target_revision
- @target_revision ||= begin
- if sha_hash?(@new_resource.revision)
- @target_revision = @new_resource.revision
- else
- @target_revision = remote_resolve_reference
+ @target_revision ||=
+ begin
+ if sha_hash?(new_resource.revision)
+ @target_revision = new_resource.revision
+ else
+ @target_revision = remote_resolve_reference
+ end
end
- end
end
alias :revision_slug :target_revision
def remote_resolve_reference
- Chef::Log.debug("#{@new_resource} resolving remote reference")
+ logger.trace("#{new_resource} resolving remote reference")
# The sha pointed to by an annotated tag is identified by the
# '^{}' suffix appended to the tag. In order to resolve
# annotated tags, we have to search for "revision*" and
@@ -250,19 +308,28 @@ class Chef
# 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, "^{}")
+ found = find_revision(refs, new_resource.revision, "^{}")
else
found = refs_search(refs, "HEAD")
end
- found = find_revision(refs, @new_resource.revision) if found.empty?
+ 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, revision + suffix) if found.empty?
- found
+ if !found.empty?
+ @is_tag = true
+ found
+ else
+ found = refs_search(refs, rev_match_pattern("refs/heads/", revision) + suffix)
+ if !found.empty?
+ @is_branch = true
+ found
+ else
+ refs_search(refs, revision + suffix)
+ end
+ end
end
def rev_match_pattern(prefix, revision)
@@ -274,61 +341,85 @@ class Chef
end
def rev_search_pattern
- if ["", "HEAD"].include? @new_resource.revision
+ if ["", "HEAD"].include? new_resource.revision
"HEAD"
else
- @new_resource.revision + "*"
+ new_resource.revision + "*"
end
end
def git_ls_remote(rev_pattern)
- git("ls-remote", "\"#{@new_resource.repository}\"", "\"#{rev_pattern}\"").stdout
+ git("ls-remote", "\"#{new_resource.repository}\"", "\"#{rev_pattern}\"").stdout
end
def refs_search(refs, pattern)
refs.find_all { |m| m[1] == pattern }
end
+ alias git_minor_version git_gem_version
+
private
+ def is_branch?
+ !!@is_branch
+ end
+
+ def is_tag?
+ !!@is_tag
+ end
+
def run_options(run_opts = {})
env = {}
- if @new_resource.user
- run_opts[:user] = @new_resource.user
+ if new_resource.user
+ run_opts[:user] = new_resource.user
# 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"
- case @new_resource.user
- when Integer
- Etc.getpwuid(@new_resource.user).dir
- else
- Etc.getpwnam(@new_resource.user.to_s).dir
- end
- 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
+ env["HOME"] = get_homedir(new_resource.user)
end
- run_opts[:group] = @new_resource.group if @new_resource.group
- 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
+ run_opts[:group] = new_resource.group if new_resource.group
+ 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
run_opts[:environment] = env unless env.empty?
run_opts
end
def git(*args, **run_opts)
git_command = ["git", args].compact.join(" ")
- Chef::Log.debug "running #{git_command}"
- shell_out!(git_command, run_options(run_opts))
+ logger.trace "running #{git_command}"
+ shell_out!(git_command, **run_options(run_opts))
end
def sha_hash?(string)
string =~ /^[0-9a-f]{40}$/
end
+ # Returns a message for sensitive repository URL if sensitive is true otherwise
+ # repository URL is returned
+ # @return [String]
+ def repo_url
+ if new_resource.sensitive
+ "**Suppressed Sensitive URL**"
+ else
+ new_resource.repository
+ end
+ end
+
+ # Returns the home directory of the user
+ # @param [String] user must be a string.
+ # @return [String] the home directory of the user.
+ #
+ def get_homedir(user)
+ require "etc" unless defined?(Etc)
+ case user
+ when Integer
+ Etc.getpwuid(user).dir
+ else
+ Etc.getpwnam(user.to_s).dir
+ end
+ end
end
end
end
diff --git a/lib/chef/provider/group.rb b/lib/chef/provider/group.rb
index 8936bd2031..0cda3182ba 100644
--- a/lib/chef/provider/group.rb
+++ b/lib/chef/provider/group.rb
@@ -1,6 +1,6 @@
#
# Author:: AJ Christensen (<aj@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,63 +16,55 @@
# limitations under the License.
#
-require "chef/provider"
-require "chef/mixin/shell_out"
-require "chef/mixin/command"
-require "etc"
+require_relative "../provider"
+require "etc" unless defined?(Etc)
class Chef
class Provider
class Group < Chef::Provider
- include Chef::Mixin::ShellOut
- include Chef::Mixin::Command
attr_accessor :group_exists
attr_accessor :change_desc
- def whyrun_supported?
- true
- end
-
def initialize(new_resource, run_context)
super
@group_exists = true
end
def load_current_resource
- @current_resource = Chef::Resource::Group.new(@new_resource.name)
- @current_resource.group_name(@new_resource.group_name)
+ @current_resource = Chef::Resource::Group.new(new_resource.name)
+ current_resource.group_name(new_resource.group_name)
group_info = nil
begin
- group_info = Etc.getgrnam(@new_resource.group_name)
- rescue ArgumentError => e
+ group_info = Etc.getgrnam(new_resource.group_name)
+ rescue ArgumentError
@group_exists = false
- Chef::Log.debug("#{@new_resource} group does not exist")
+ logger.trace("#{new_resource} group does not exist")
end
if group_info
- @new_resource.gid(group_info.gid) unless @new_resource.gid
- @current_resource.gid(group_info.gid)
- @current_resource.members(group_info.mem)
+ new_resource.gid(group_info.gid) unless new_resource.gid
+ current_resource.gid(group_info.gid)
+ current_resource.members(group_info.mem)
end
- @current_resource
+ current_resource
end
def define_resource_requirements
requirements.assert(:modify) do |a|
a.assertion { @group_exists }
- a.failure_message(Chef::Exceptions::Group, "Cannot modify #{@new_resource} - group does not exist!")
- a.whyrun("Group #{@new_resource} does not exist. Unless it would have been created earlier in this run, this attempt to modify it would fail.")
+ a.failure_message(Chef::Exceptions::Group, "Cannot modify #{new_resource} - group does not exist!")
+ a.whyrun("Group #{new_resource} does not exist. Unless it would have been created earlier in this run, this attempt to modify it would fail.")
end
requirements.assert(:all_actions) do |a|
# Make sure that the resource doesn't contain any common
# user names in the members and exclude_members properties.
- if !@new_resource.members.nil? && !@new_resource.excluded_members.nil?
- common_members = @new_resource.members & @new_resource.excluded_members
+ if !new_resource.members.nil? && !new_resource.excluded_members.nil?
+ common_members = new_resource.members & new_resource.excluded_members
a.assertion { common_members.empty? }
- a.failure_message(Chef::Exceptions::ConflictingMembersInGroup, "Attempting to both add and remove users from a group: '#{common_members.join(', ')}'")
+ a.failure_message(Chef::Exceptions::ConflictingMembersInGroup, "Attempting to both add and remove users from a group: '#{common_members.join(", ")}'")
# No why-run alternative
end
end
@@ -86,41 +78,48 @@ class Chef
# <false>:: If a change is not required
def compare_group
@change_desc = [ ]
- if @new_resource.gid.to_s != @current_resource.gid.to_s
- @change_desc << "change gid #{@current_resource.gid} to #{@new_resource.gid}"
+ unless group_gid_match?
+ @change_desc << "change gid #{current_resource.gid} to #{new_resource.gid}"
end
- if @new_resource.append
+ if new_resource.append
missing_members = []
- @new_resource.members.each do |member|
+ new_resource.members.each do |member|
next if has_current_group_member?(member)
+
validate_member!(member)
missing_members << member
end
- if missing_members.length > 0
+ unless missing_members.empty?
@change_desc << "add missing member(s): #{missing_members.join(", ")}"
end
members_to_be_removed = []
- @new_resource.excluded_members.each do |member|
+ new_resource.excluded_members.each do |member|
if has_current_group_member?(member)
members_to_be_removed << member
end
end
- if members_to_be_removed.length > 0
+ unless members_to_be_removed.empty?
@change_desc << "remove existing member(s): #{members_to_be_removed.join(", ")}"
end
- else
- if @new_resource.members != @current_resource.members
- @change_desc << "replace group members with new list of members"
- end
+ elsif !group_members_match?
+ @change_desc << "replace group members with new list of members: #{new_resource.members.join(", ")}"
end
!@change_desc.empty?
end
+ def group_gid_match?
+ new_resource.gid.to_s == current_resource.gid.to_s
+ end
+
+ def group_members_match?
+ [new_resource.members].flatten.sort == [current_resource.members].flatten.sort
+ end
+
def has_current_group_member?(member)
- @current_resource.members.include?(member)
+ current_resource.members.include?(member)
end
def validate_member!(member)
@@ -129,47 +128,47 @@ class Chef
true
end
- def action_create
+ action :create do
case @group_exists
when false
- converge_by("create group #{@new_resource.group_name}") do
+ converge_by("create group #{new_resource.group_name}") do
create_group
- Chef::Log.info("#{@new_resource} created")
+ logger.info("#{new_resource} created")
end
else
if compare_group
- converge_by(["alter group #{@new_resource.group_name}"] + change_desc) do
+ converge_by(["alter group #{new_resource.group_name}"] + change_desc) do
manage_group
- Chef::Log.info("#{@new_resource} altered")
+ logger.info("#{new_resource} altered: #{change_desc.join(", ")}")
end
end
end
end
- def action_remove
- if @group_exists
- converge_by("remove group #{@new_resource.group_name}") do
- remove_group
- Chef::Log.info("#{@new_resource} removed")
- end
+ action :remove do
+ return unless @group_exists
+
+ converge_by("remove group #{new_resource.group_name}") do
+ remove_group
+ logger.info("#{new_resource} removed")
end
end
- def action_manage
- if @group_exists && compare_group
- converge_by(["manage group #{@new_resource.group_name}"] + change_desc) do
- manage_group
- Chef::Log.info("#{@new_resource} managed")
- end
+ action :manage do
+ return unless @group_exists && compare_group
+
+ converge_by(["manage group #{new_resource.group_name}"] + change_desc) do
+ manage_group
+ logger.info("#{new_resource} managed: #{change_desc.join(", ")}")
end
end
- def action_modify
- if compare_group
- converge_by(["modify group #{@new_resource.group_name}"] + change_desc) do
- manage_group
- Chef::Log.info("#{@new_resource} modified")
- end
+ action :modify do
+ return unless compare_group
+
+ converge_by(["modify group #{new_resource.group_name}"] + change_desc) do
+ manage_group
+ logger.info("#{new_resource} modified: #{change_desc.join(", ")}")
end
end
diff --git a/lib/chef/provider/group/aix.rb b/lib/chef/provider/group/aix.rb
index 4a02d5ef8c..aa4d8ba4c4 100644
--- a/lib/chef/provider/group/aix.rb
+++ b/lib/chef/provider/group/aix.rb
@@ -16,8 +16,7 @@
# limitations under the License.
#
-require "chef/provider/group/groupadd"
-require "chef/mixin/shell_out"
+require_relative "groupadd"
class Chef
class Provider
@@ -33,48 +32,44 @@ class Chef
end
def create_group
- command = "mkgroup"
- command << set_options << " #{@new_resource.group_name}"
- run_command(:command => command)
+ shell_out!("mkgroup", set_options, new_resource.group_name)
modify_group_members
end
def manage_group
- command = "chgroup"
options = set_options
- #Usage: chgroup [-R load_module] "attr=value" ... group
if options.size > 0
- command << options << " #{@new_resource.group_name}"
- run_command(:command => command)
+ shell_out!("chgroup", options, new_resource.group_name)
end
modify_group_members
end
def remove_group
- run_command(:command => "rmgroup #{@new_resource.group_name}")
+ shell_out!("rmgroup", new_resource.group_name)
end
def add_member(member)
- shell_out!("chgrpmem -m + #{member} #{@new_resource.group_name}")
+ shell_out!("chgrpmem", "-m", "+", member, new_resource.group_name)
end
def set_members(members)
return if members.empty?
- shell_out!("chgrpmem -m = #{members.join(',')} #{@new_resource.group_name}")
+
+ shell_out!("chgrpmem", "-m", "=", members.join(","), new_resource.group_name)
end
def remove_member(member)
- shell_out!("chgrpmem -m - #{member} #{@new_resource.group_name}")
+ shell_out!("chgrpmem", "-m", "-", member, new_resource.group_name)
end
def set_options
- opts = ""
- { :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 #{@new_resource.send(field)}")
- opts << " '#{option}=#{@new_resource.send(field)}'"
- end
+ opts = []
+ { gid: "id" }.sort_by { |a| a[0] }.each do |field, option|
+ next unless current_resource.send(field) != new_resource.send(field)
+
+ if new_resource.send(field)
+ logger.trace("#{new_resource} setting #{field} to #{new_resource.send(field)}")
+ opts << "#{option}=#{new_resource.send(field)}"
end
end
opts
diff --git a/lib/chef/provider/group/dscl.rb b/lib/chef/provider/group/dscl.rb
index 00b4ce2b93..824ebe0477 100644
--- a/lib/chef/provider/group/dscl.rb
+++ b/lib/chef/provider/group/dscl.rb
@@ -1,6 +1,6 @@
#
# Author:: Dreamcat4 (<dreamcat4@gmail.com>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,31 +24,35 @@ class Chef
provides :group, os: "darwin"
def dscl(*args)
- host = "."
- stdout_result = ""; stderr_result = ""; cmd = "dscl #{host} -#{args.join(' ')}"
- status = shell_out(cmd)
+ argdup = args.dup
+ cmd = argdup.shift
+ shellcmd = [ "dscl", ".", "-#{cmd}", argdup ]
+ status = shell_out(shellcmd)
+ stdout_result = ""
+ stderr_result = ""
status.stdout.each_line { |line| stdout_result << line }
status.stderr.each_line { |line| stderr_result << line }
- return [cmd, status, stdout_result, stderr_result]
+ [shellcmd.flatten.compact.join(" "), status, stdout_result, stderr_result]
end
def safe_dscl(*args)
result = dscl(*args)
return "" if ( args.first =~ /^delete/ ) && ( result[1].exitstatus != 0 )
raise(Chef::Exceptions::Group, "dscl error: #{result.inspect}") unless result[1].exitstatus == 0
- raise(Chef::Exceptions::Group, "dscl error: #{result.inspect}") if result[2] =~ /No such key: /
- return result[2]
+ raise(Chef::Exceptions::Group, "dscl error: #{result.inspect}") if /No such key: /.match?(result[2])
+
+ result[2]
end
def load_current_resource
- @current_resource = Chef::Resource::Group.new(@new_resource.name)
- @current_resource.group_name(@new_resource.group_name)
+ @current_resource = Chef::Resource::Group.new(new_resource.name)
+ current_resource.group_name(new_resource.group_name)
group_info = nil
begin
- group_info = safe_dscl("read /Groups/#{@new_resource.group_name}")
+ group_info = safe_dscl("read", "/Groups/#{new_resource.group_name}")
rescue Chef::Exceptions::Group
@group_exists = false
- Chef::Log.debug("#{@new_resource} group does not exist")
+ logger.trace("#{new_resource} group does not exist")
end
if group_info
@@ -57,74 +61,80 @@ class Chef
val.strip! if val
case key.downcase
when "primarygroupid"
- @new_resource.gid(val) unless @new_resource.gid
- @current_resource.gid(val)
+ new_resource.gid(val) unless new_resource.gid
+ current_resource.gid(val)
when "groupmembership"
- @current_resource.members(val.split(" "))
+ current_resource.members(val.split(" "))
end
end
end
- @current_resource
+ current_resource
end
# get a free GID greater than 200
def get_free_gid(search_limit = 1000)
gid = nil; next_gid_guess = 200
- groups_gids = safe_dscl("list /Groups gid")
+ groups_gids = safe_dscl("list", "/Groups", "gid")
while next_gid_guess < search_limit + 200
- if groups_gids =~ Regexp.new("#{Regexp.escape(next_gid_guess.to_s)}\n")
+ if groups_gids&.match?(Regexp.new("#{Regexp.escape(next_gid_guess.to_s)}\n"))
next_gid_guess += 1
else
gid = next_gid_guess
break
end
end
- return gid || raise("gid not found. Exhausted. Searched #{search_limit} times")
+ gid || raise("gid not found. Exhausted. Searched #{search_limit} times")
end
def gid_used?(gid)
return false unless gid
- groups_gids = safe_dscl("list /Groups gid")
- !! ( groups_gids =~ Regexp.new("#{Regexp.escape(gid.to_s)}\n") )
+
+ search_gids = safe_dscl("search", "/Groups", "PrimaryGroupID", gid.to_s)
+
+ # dscl -search should not return anything if the gid doesn't exist,
+ # but on the off-chance that it does, check whether the given gid is
+ # in the output.
+ !!(search_gids =~ /\b#{gid}\b/)
end
def set_gid
- @new_resource.gid(get_free_gid) if [nil, ""].include? @new_resource.gid
- raise(Chef::Exceptions::Group, "gid is already in use") if gid_used?(@new_resource.gid)
- safe_dscl("create /Groups/#{@new_resource.group_name} PrimaryGroupID #{@new_resource.gid}")
+ new_resource.gid(get_free_gid) if [nil, ""].include? new_resource.gid
+ raise(Chef::Exceptions::Group, "gid is already in use") if gid_used?(new_resource.gid)
+
+ safe_dscl("create", "/Groups/#{new_resource.group_name}", "PrimaryGroupID", new_resource.gid)
end
def set_members
# First reset the memberships if the append is not set
- unless @new_resource.append
- Chef::Log.debug("#{@new_resource} removing group members #{@current_resource.members.join(' ')}") unless @current_resource.members.empty?
- safe_dscl("create /Groups/#{@new_resource.group_name} GroupMembers ''") # clear guid list
- safe_dscl("create /Groups/#{@new_resource.group_name} GroupMembership ''") # clear user list
- @current_resource.members([ ])
+ unless new_resource.append
+ logger.trace("#{new_resource} removing group members #{current_resource.members.join(" ")}") unless current_resource.members.empty?
+ safe_dscl("create", "/Groups/#{new_resource.group_name}", "GroupMembers", "") # clear guid list
+ safe_dscl("create", "/Groups/#{new_resource.group_name}", "GroupMembership", "") # clear user list
+ current_resource.members([ ])
end
# Add any members that need to be added
- if @new_resource.members && !@new_resource.members.empty?
+ if new_resource.members && !new_resource.members.empty?
members_to_be_added = [ ]
- @new_resource.members.each do |member|
- members_to_be_added << member if !@current_resource.members.include?(member)
+ new_resource.members.each do |member|
+ members_to_be_added << member unless current_resource.members.include?(member)
end
unless members_to_be_added.empty?
- Chef::Log.debug("#{@new_resource} setting group members #{members_to_be_added.join(', ')}")
- safe_dscl("append /Groups/#{@new_resource.group_name} GroupMembership #{members_to_be_added.join(' ')}")
+ logger.trace("#{new_resource} setting group members #{members_to_be_added.join(", ")}")
+ safe_dscl("append", "/Groups/#{new_resource.group_name}", "GroupMembership", *members_to_be_added)
end
end
# Remove any members that need to be removed
- if @new_resource.excluded_members && !@new_resource.excluded_members.empty?
+ if new_resource.excluded_members && !new_resource.excluded_members.empty?
members_to_be_removed = [ ]
- @new_resource.excluded_members.each do |member|
- members_to_be_removed << member if @current_resource.members.include?(member)
+ new_resource.excluded_members.each do |member|
+ members_to_be_removed << member if current_resource.members.include?(member)
end
unless members_to_be_removed.empty?
- Chef::Log.debug("#{@new_resource} removing group members #{members_to_be_removed.join(', ')}")
- safe_dscl("delete /Groups/#{@new_resource.group_name} GroupMembership #{members_to_be_removed.join(' ')}")
+ logger.trace("#{new_resource} removing group members #{members_to_be_removed.join(", ")}")
+ safe_dscl("delete", "/Groups/#{new_resource.group_name}", "GroupMembership", *members_to_be_removed)
end
end
end
@@ -132,8 +142,8 @@ class Chef
def define_resource_requirements
super
requirements.assert(:all_actions) do |a|
- a.assertion { ::File.exists?("/usr/bin/dscl") }
- a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/bin/dscl for #{@new_resource.name}"
+ a.assertion { ::File.exist?("/usr/bin/dscl") }
+ a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/bin/dscl for #{new_resource.name}"
# No whyrun alternative: this component should be available in the base install of any given system that uses it
end
end
@@ -145,24 +155,24 @@ class Chef
end
def manage_group
- if @new_resource.group_name && (@current_resource.group_name != @new_resource.group_name)
+ if new_resource.group_name && (current_resource.group_name != new_resource.group_name)
dscl_create_group
end
- if @new_resource.gid && (@current_resource.gid != @new_resource.gid)
+ if new_resource.gid && (current_resource.gid != new_resource.gid)
set_gid
end
- if @new_resource.members || @new_resource.excluded_members
+ if new_resource.members || new_resource.excluded_members
set_members
end
end
def dscl_create_group
- safe_dscl("create /Groups/#{@new_resource.group_name}")
- safe_dscl("create /Groups/#{@new_resource.group_name} Password '*'")
+ safe_dscl("create", "/Groups/#{new_resource.group_name}")
+ safe_dscl("create", "/Groups/#{new_resource.group_name}", "Password", "*")
end
def remove_group
- safe_dscl("delete /Groups/#{@new_resource.group_name}")
+ safe_dscl("delete", "/Groups/#{new_resource.group_name}")
end
end
end
diff --git a/lib/chef/provider/group/gpasswd.rb b/lib/chef/provider/group/gpasswd.rb
index dcf526b211..937a417dee 100644
--- a/lib/chef/provider/group/gpasswd.rb
+++ b/lib/chef/provider/group/gpasswd.rb
@@ -1,6 +1,6 @@
#
# Author:: AJ Christensen (<aj@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/provider/group/groupadd"
+require_relative "groupadd"
class Chef
class Provider
@@ -31,26 +31,26 @@ class Chef
def define_resource_requirements
super
requirements.assert(:all_actions) do |a|
- a.assertion { ::File.exists?("/usr/bin/gpasswd") }
- a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/bin/gpasswd for #{@new_resource}"
+ a.assertion { ::File.exist?("/usr/bin/gpasswd") }
+ a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/bin/gpasswd for #{new_resource}"
# No whyrun alternative: this component should be available in the base install of any given system that uses it
end
end
def set_members(members)
- unless members.empty?
- shell_out!("gpasswd -M #{members.join(',')} #{@new_resource.group_name}")
+ if members.empty?
+ shell_out!("gpasswd", "-M", "", new_resource.group_name)
else
- shell_out!("gpasswd -M \"\" #{@new_resource.group_name}")
+ shell_out!("gpasswd", "-M", members.join(","), new_resource.group_name)
end
end
def add_member(member)
- shell_out!("gpasswd -a #{member} #{@new_resource.group_name}")
+ shell_out!("gpasswd", "-a", member, new_resource.group_name)
end
def remove_member(member)
- shell_out!("gpasswd -d #{member} #{@new_resource.group_name}")
+ shell_out!("gpasswd", "-d", member, new_resource.group_name)
end
end
end
diff --git a/lib/chef/provider/group/groupadd.rb b/lib/chef/provider/group/groupadd.rb
index bc6b5d0208..63f1690f5e 100644
--- a/lib/chef/provider/group/groupadd.rb
+++ b/lib/chef/provider/group/groupadd.rb
@@ -1,6 +1,6 @@
#
# Author:: AJ Christensen (<aj@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -35,8 +35,8 @@ class Chef
super
required_binaries.each do |required_binary|
requirements.assert(:all_actions) do |a|
- a.assertion { ::File.exists?(required_binary) }
- a.failure_message Chef::Exceptions::Group, "Could not find binary #{required_binary} for #{@new_resource}"
+ a.assertion { ::File.exist?(required_binary) }
+ a.failure_message Chef::Exceptions::Group, "Could not find binary #{required_binary} for #{new_resource}"
# No whyrun alternative: this component should be available in the base install of any given system that uses it
end
end
@@ -44,54 +44,49 @@ class Chef
# Create the group
def create_group
- command = "groupadd"
- command << set_options
- command << groupadd_options
- run_command(:command => command)
+ shell_out!("groupadd", set_options, groupadd_options)
modify_group_members
end
# Manage the group when it already exists
def manage_group
- command = "groupmod"
- command << set_options
- run_command(:command => command)
+ shell_out!("groupmod", set_options)
modify_group_members
end
# Remove the group
def remove_group
- run_command(:command => "groupdel #{@new_resource.group_name}")
+ shell_out!("groupdel", new_resource.group_name)
end
def modify_group_members
- if @new_resource.append
- if @new_resource.members && !@new_resource.members.empty?
+ if new_resource.append
+ if new_resource.members && !new_resource.members.empty?
members_to_be_added = [ ]
- @new_resource.members.each do |member|
- members_to_be_added << member if !@current_resource.members.include?(member)
+ new_resource.members.each do |member|
+ members_to_be_added << member unless current_resource.members.include?(member)
end
members_to_be_added.each do |member|
- Chef::Log.debug("#{@new_resource} appending member #{member} to group #{@new_resource.group_name}")
+ logger.trace("#{new_resource} appending member #{member} to group #{new_resource.group_name}")
add_member(member)
end
end
- if @new_resource.excluded_members && !@new_resource.excluded_members.empty?
+ if new_resource.excluded_members && !new_resource.excluded_members.empty?
members_to_be_removed = [ ]
- @new_resource.excluded_members.each do |member|
- members_to_be_removed << member if @current_resource.members.include?(member)
+ new_resource.excluded_members.each do |member|
+ members_to_be_removed << member if current_resource.members.include?(member)
end
members_to_be_removed.each do |member|
- Chef::Log.debug("#{@new_resource} removing member #{member} from group #{@new_resource.group_name}")
+ logger.trace("#{new_resource} removing member #{member} from group #{new_resource.group_name}")
remove_member(member)
end
end
else
- members_description = @new_resource.members.empty? ? "none" : @new_resource.members.join(", ")
- Chef::Log.debug("#{@new_resource} setting group members to: #{members_description}")
- set_members(@new_resource.members)
+ members_description = new_resource.members.empty? ? "none" : new_resource.members.join(", ")
+ logger.trace("#{new_resource} setting group members to: #{members_description}")
+ set_members(new_resource.members)
end
end
@@ -112,22 +107,24 @@ class Chef
# ==== Returns
# <string>:: A string containing the option and then the quoted value
def set_options
- opts = ""
- { :gid => "-g" }.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)
- opts << " #{option} '#{@new_resource.send(field)}'"
- Chef::Log.debug("#{@new_resource} set #{field} to #{@new_resource.send(field)}")
- end
- end
+ opts = []
+ { gid: "-g" }.sort_by { |a| a[0] }.each do |field, option|
+ next unless current_resource.send(field) != new_resource.send(field)
+ next unless new_resource.send(field)
+
+ opts << option
+ opts << new_resource.send(field)
+ logger.trace("#{new_resource} set #{field} to #{new_resource.send(field)}")
end
- opts << " #{@new_resource.group_name}"
+ opts << new_resource.group_name
+ opts
end
def groupadd_options
- opts = ""
- opts << " -r" if @new_resource.system
- opts << " -o" if @new_resource.non_unique
+ opts = []
+ # Solaris doesn't support system groups.
+ opts << "-r" if new_resource.system && !node.platform?("solaris2")
+ opts << "-o" if new_resource.non_unique
opts
end
diff --git a/lib/chef/provider/group/groupmod.rb b/lib/chef/provider/group/groupmod.rb
index 295d3b3425..e4d69f34bf 100644
--- a/lib/chef/provider/group/groupmod.rb
+++ b/lib/chef/provider/group/groupmod.rb
@@ -1,6 +1,6 @@
#
# Author:: Dan Crosta (<dcrosta@late.am>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,28 +26,26 @@ class Chef
def load_current_resource
super
%w{group user}.each do |binary|
- raise Chef::Exceptions::Group, "Could not find binary /usr/sbin/#{binary} for #{@new_resource}" unless ::File.exists?("/usr/sbin/#{binary}")
+ raise Chef::Exceptions::Group, "Could not find binary /usr/sbin/#{binary} for #{new_resource}" unless ::File.exist?("/usr/sbin/#{binary}")
end
end
# Create the group
def create_group
- command = "group add"
- command << set_options
- shell_out!(command)
+ shell_out!("group", "add", set_options)
- add_group_members(@new_resource.members)
+ add_group_members(new_resource.members)
end
# Manage the group when it already exists
def manage_group
- if @new_resource.append
+ if new_resource.append
members_to_be_added = [ ]
- if @new_resource.excluded_members && !@new_resource.excluded_members.empty?
+ if new_resource.excluded_members && !new_resource.excluded_members.empty?
# First find out if any member needs to be removed
members_to_be_removed = [ ]
- @new_resource.excluded_members.each do |member|
- members_to_be_removed << member if @current_resource.members.include?(member)
+ new_resource.excluded_members.each do |member|
+ members_to_be_removed << member if current_resource.members.include?(member)
end
unless members_to_be_removed.empty?
@@ -56,39 +54,39 @@ class Chef
# Capture the members we need to add in
# members_to_be_added to be added later on.
- @current_resource.members.each do |member|
+ current_resource.members.each do |member|
members_to_be_added << member unless members_to_be_removed.include?(member)
end
end
end
- if @new_resource.members && !@new_resource.members.empty?
- @new_resource.members.each do |member|
- members_to_be_added << member if !@current_resource.members.include?(member)
+ if new_resource.members && !new_resource.members.empty?
+ new_resource.members.each do |member|
+ members_to_be_added << member unless current_resource.members.include?(member)
end
end
- Chef::Log.debug("#{@new_resource} not changing group members, the group has no members to add") if members_to_be_added.empty?
+ logger.trace("#{new_resource} not changing group members, the group has no members to add") if members_to_be_added.empty?
add_group_members(members_to_be_added)
else
# We are resetting the members of a group so use the same trick
reset_group_membership
- Chef::Log.debug("#{@new_resource} setting group members to: none") if @new_resource.members.empty?
- add_group_members(@new_resource.members)
+ logger.trace("#{new_resource} setting group members to: none") if new_resource.members.empty?
+ add_group_members(new_resource.members)
end
end
# Remove the group
def remove_group
- shell_out!("group del #{@new_resource.group_name}")
+ shell_out!("group", "del", new_resource.group_name)
end
# Adds a list of usernames to the group using `user mod`
def add_group_members(members)
- Chef::Log.debug("#{@new_resource} adding members #{members.join(', ')}") if !members.empty?
+ logger.trace("#{new_resource} adding members #{members.join(", ")}") unless members.empty?
members.each do |user|
- shell_out!("user mod -G #{@new_resource.group_name} #{user}")
+ shell_out!("user", "mod", "-G", new_resource.group_name, user)
end
end
@@ -96,15 +94,11 @@ class Chef
# "<name>_bak", create a new group with the same GID and
# "<name>", then set correct members on that group
def reset_group_membership
- rename = "group mod -n #{@new_resource.group_name}_bak #{@new_resource.group_name}"
- shell_out!(rename)
+ shell_out!("group", "mod", "-n", "#{new_resource.group_name}_bak", new_resource.group_name)
- create = "group add"
- create << set_options(:overwrite_gid => true)
- shell_out!(create)
+ shell_out!("group", "add", set_options(overwrite_gid: true))
- remove = "group del #{@new_resource.group_name}_bak"
- shell_out!(remove)
+ shell_out!("group", "del", "#{new_resource.group_name}_bak")
end
# Little bit of magic as per Adam's useradd provider to pull and assign the command line flags
@@ -112,14 +106,15 @@ class Chef
# ==== Returns
# <string>:: A string containing the option and then the quoted value
def set_options(overwrite_gid = false)
- opts = ""
- if overwrite_gid || @new_resource.gid && (@current_resource.gid != @new_resource.gid)
- opts << " -g '#{@new_resource.gid}'"
+ opts = []
+ if overwrite_gid || new_resource.gid && (current_resource.gid != new_resource.gid)
+ opts << "-g"
+ opts << new_resource.gid
end
if overwrite_gid
- opts << " -o"
+ opts << "-o"
end
- opts << " #{@new_resource.group_name}"
+ opts << new_resource.group_name
opts
end
end
diff --git a/lib/chef/provider/group/pw.rb b/lib/chef/provider/group/pw.rb
index 4fd78b6b31..0639b8fdb5 100644
--- a/lib/chef/provider/group/pw.rb
+++ b/lib/chef/provider/group/pw.rb
@@ -1,6 +1,6 @@
#
# Author:: Stephen Haynes (<sh@nomitor.com>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,46 +30,42 @@ class Chef
super
requirements.assert(:all_actions) do |a|
- a.assertion { ::File.exists?("/usr/sbin/pw") }
- a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/pw for #{@new_resource}"
+ a.assertion { ::File.exist?("/usr/sbin/pw") }
+ a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/pw for #{new_resource}"
# No whyrun alternative: this component should be available in the base install of any given system that uses it
end
end
# Create the group
def create_group
- command = "pw groupadd"
- command << set_options
-
- unless @new_resource.members.empty?
+ command = [ "pw", "groupadd", set_options ]
+ unless new_resource.members.empty?
# pw group[add|mod] -M is used to set the full membership list on a
# new or existing group. Because pw groupadd does not support the -m
# and -d options used by manage_group, we treat group creation as a
# special case and use -M.
- Chef::Log.debug("#{@new_resource} setting group members: #{@new_resource.members.join(',')}")
- command += " -M #{@new_resource.members.join(',')}"
+ logger.trace("#{new_resource} setting group members: #{new_resource.members.join(",")}")
+ command += [ "-M", new_resource.members.join(",") ]
end
- run_command(:command => command)
+ shell_out!(command)
end
# Manage the group when it already exists
def manage_group
- command = "pw groupmod"
- command << set_options
member_options = set_members_options
if member_options.empty?
- run_command(:command => command)
+ shell_out!("pw", "groupmod", set_options)
else
member_options.each do |option|
- run_command(:command => command + option)
+ shell_out!("pw", "groupmod", set_options, option)
end
end
end
# Remove the group
def remove_group
- run_command(:command => "pw groupdel #{@new_resource.group_name}")
+ shell_out!("pw", "groupdel", new_resource.group_name)
end
# Little bit of magic as per Adam's useradd provider to pull and assign the command line flags
@@ -77,10 +73,11 @@ class Chef
# ==== Returns
# <string>:: A string containing the option and then the quoted value
def set_options
- opts = " #{@new_resource.group_name}"
- if @new_resource.gid && (@current_resource.gid != @new_resource.gid)
- Chef::Log.debug("#{@new_resource}: current gid (#{@current_resource.gid}) doesnt match target gid (#{@new_resource.gid}), changing it")
- opts << " -g '#{@new_resource.gid}'"
+ opts = [ new_resource.group_name ]
+ if new_resource.gid && (current_resource.gid != new_resource.gid)
+ logger.trace("#{new_resource}: current gid (#{current_resource.gid}) doesn't match target gid (#{new_resource.gid}), changing it")
+ opts << "-g"
+ opts << new_resource.gid
end
opts
end
@@ -91,26 +88,26 @@ class Chef
members_to_be_added = [ ]
members_to_be_removed = [ ]
- if @new_resource.append
+ if new_resource.append
# Append is set so we will only add members given in the
# members list and remove members given in the
# excluded_members list.
- if @new_resource.members && !@new_resource.members.empty?
- @new_resource.members.each do |member|
- members_to_be_added << member if !@current_resource.members.include?(member)
+ if new_resource.members && !new_resource.members.empty?
+ new_resource.members.each do |member|
+ members_to_be_added << member unless current_resource.members.include?(member)
end
end
- if @new_resource.excluded_members && !@new_resource.excluded_members.empty?
- @new_resource.excluded_members.each do |member|
- members_to_be_removed << member if @current_resource.members.include?(member)
+ if new_resource.excluded_members && !new_resource.excluded_members.empty?
+ new_resource.excluded_members.each do |member|
+ members_to_be_removed << member if current_resource.members.include?(member)
end
end
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.dup
- @current_resource.members.each do |member|
+ 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
if members_to_be_added.include? member
@@ -122,13 +119,13 @@ class Chef
end
unless members_to_be_added.empty?
- Chef::Log.debug("#{@new_resource} adding group members: #{members_to_be_added.join(',')}")
- opts << " -m #{members_to_be_added.join(',')}"
+ logger.trace("#{new_resource} adding group members: #{members_to_be_added.join(",")}")
+ opts << [ "-m", members_to_be_added.join(",") ]
end
unless members_to_be_removed.empty?
- Chef::Log.debug("#{@new_resource} removing group members: #{members_to_be_removed.join(',')}")
- opts << " -d #{members_to_be_removed.join(',')}"
+ logger.trace("#{new_resource} removing group members: #{members_to_be_removed.join(",")}")
+ opts << [ "-d", members_to_be_removed.join(",") ]
end
opts
diff --git a/lib/chef/provider/group/solaris.rb b/lib/chef/provider/group/solaris.rb
new file mode 100644
index 0000000000..9a3e1e2b35
--- /dev/null
+++ b/lib/chef/provider/group/solaris.rb
@@ -0,0 +1,62 @@
+#
+# Author:: Joshua Justice (<jjustice6@bloomberg.net>)
+# Copyright:: Copyright (c) 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_relative "groupadd"
+
+class Chef
+ class Provider
+ class Group
+ class Solaris < Chef::Provider::Group::Groupadd
+
+ # this provides line is setup to only catch the solaris2 platform, but
+ # NOT other platforms in the Solaris platform_family. (See usermod provider.)
+ provides :group, platform: "solaris2"
+
+ def load_current_resource
+ super
+ end
+
+ def define_resource_requirements
+ super
+
+ requirements.assert(:all_actions) do |a|
+ a.assertion { ::File.exist?("/usr/sbin/usermod") && ::File.exist?("/usr/sbin/groupmod") }
+ a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/usermod or /usr/sbin/groupmod for #{new_resource}"
+ # No whyrun alternative: this component should be available in the base install of any given system that uses it
+ end
+ end
+
+ def set_members(members)
+ # Set the group to have exactly the list of members passed to it.
+ unless members.empty?
+ shell_out!("groupmod", "-U", members.join(","), new_resource.group_name)
+ end
+ end
+
+ def add_member(member)
+ shell_out!("usermod", "-G", "+#{new_resource.group_name}", member)
+ end
+
+ def remove_member(member)
+ shell_out!("usermod", "-G", "-#{new_resource.group_name}", member)
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/group/suse.rb b/lib/chef/provider/group/suse.rb
index a79038e25e..266e8e0fbc 100644
--- a/lib/chef/provider/group/suse.rb
+++ b/lib/chef/provider/group/suse.rb
@@ -1,6 +1,6 @@
#
# Author:: AJ Christensen (<aj@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,13 +16,13 @@
# limitations under the License.
#
-require "chef/provider/group/groupadd"
+require_relative "groupadd"
+require "etc" unless defined?(Etc)
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
@@ -32,30 +32,48 @@ class Chef
def define_resource_requirements
super
requirements.assert(:all_actions) do |a|
- a.assertion { ::File.exists?("/usr/sbin/groupmod") }
- a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/groupmod for #{@new_resource.name}"
+ a.assertion { ::File.exist?("/usr/sbin/groupmod") }
+ a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/groupmod for #{new_resource.name}"
# No whyrun alternative: this component should be available in the base install of any given system that uses it
end
+
+ requirements.assert(:create, :manage, :modify) do |a|
+ a.assertion do
+
+ to_add(new_resource.members).all? { |member| Etc.getpwnam(member) }
+ rescue
+ false
+
+ end
+ a.failure_message Chef::Exceptions::Group, "Could not add users #{to_add(new_resource.members).join(", ")} to #{new_resource.group_name}: one of these users does not exist"
+ a.whyrun "Could not find one of these users: #{to_add(new_resource.members).join(", ")}. Assuming it will be created by a prior step"
+ end
end
def set_members(members)
- to_delete = @current_resource.members - members
- to_delete.each do |member|
+ to_remove(members).each do |member|
remove_member(member)
end
- to_add = members - @current_resource.members
- to_add.each do |member|
+ to_add(members).each do |member|
add_member(member)
end
end
+ def to_add(members)
+ members - current_resource.members
+ end
+
def add_member(member)
- shell_out!("groupmod -A #{member} #{@new_resource.group_name}")
+ shell_out!("groupmod", "-A", member, new_resource.group_name)
+ end
+
+ def to_remove(members)
+ current_resource.members - members
end
def remove_member(member)
- shell_out!("groupmod -R #{member} #{@new_resource.group_name}")
+ shell_out!("groupmod", "-R", member, new_resource.group_name)
end
end
diff --git a/lib/chef/provider/group/usermod.rb b/lib/chef/provider/group/usermod.rb
index bef4b667a2..e00380659d 100644
--- a/lib/chef/provider/group/usermod.rb
+++ b/lib/chef/provider/group/usermod.rb
@@ -1,6 +1,6 @@
#
# Author:: AJ Christensen (<aj@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,15 +16,14 @@
# limitations under the License.
#
-require "chef/provider/group/groupadd"
+require_relative "groupadd"
class Chef
class Provider
class Group
class Usermod < Chef::Provider::Group::Groupadd
- provides :group, os: %w{openbsd solaris2 hpux}
- provides :group, platform: "opensuse"
+ provides :group, os: %w{openbsd solaris2}
def load_current_resource
super
@@ -34,19 +33,19 @@ class Chef
super
requirements.assert(:all_actions) do |a|
- a.assertion { ::File.exists?("/usr/sbin/usermod") }
- a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/usermod for #{@new_resource}"
+ a.assertion { ::File.exist?("/usr/sbin/usermod") }
+ a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/usermod for #{new_resource}"
# No whyrun alternative: this component should be available in the base install of any given system that uses it
end
requirements.assert(:modify, :manage) do |a|
- a.assertion { @new_resource.members.empty? || @new_resource.append }
+ a.assertion { new_resource.members.empty? || new_resource.append }
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.assertion { new_resource.excluded_members.empty? }
a.failure_message Chef::Exceptions::Group, "excluded_members is not supported by #{self}"
# No whyrun alternative - this action is simply not supported.
end
@@ -57,17 +56,17 @@ class Chef
# This provider only supports adding members with
# append. Only if the action is create we will go
# ahead and add members.
- if @new_resource.action.include?(:create)
- members.each do |member|
- add_member(member)
- end
- else
+ unless new_resource.action.include?(:create)
raise Chef::Exceptions::UnsupportedAction, "Setting members directly is not supported by #{self}"
end
+
+ members.each do |member|
+ add_member(member)
+ end
end
def add_member(member)
- shell_out!("usermod #{append_flags} #{@new_resource.group_name} #{member}")
+ shell_out!("usermod", append_flags, new_resource.group_name, member)
end
def remove_member(member)
@@ -77,12 +76,7 @@ class Chef
end
def append_flags
- case node[:platform]
- when "openbsd", "netbsd", "aix", "solaris2", "smartos", "omnios"
- "-G"
- when "solaris", "suse", "opensuse"
- "-a -G"
- end
+ "-G" if platform?("openbsd", "netbsd", "aix", "smartos", "omnios")
end
end
diff --git a/lib/chef/provider/group/windows.rb b/lib/chef/provider/group/windows.rb
index 5873e42a6b..dacfc348f7 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"
-if RUBY_PLATFORM =~ /mswin|mingw32|windows/
- require "chef/util/windows/net_group"
+require_relative "../user"
+if RUBY_PLATFORM.match?(/mswin|mingw32|windows/)
+ require_relative "../../util/windows/net_group"
end
class Chef
@@ -30,26 +30,37 @@ class Chef
def initialize(new_resource, run_context)
super
- @net_group = Chef::Util::Windows::NetGroup.new(@new_resource.group_name)
+ @net_group = Chef::Util::Windows::NetGroup.new(new_resource.group_name)
+ end
+
+ def group_members_match?
+ sorted_members_sids = new_resource.members.map { |x| lookup_account_name(x) }.sort
+ sorted_current_sids = current_resource.members.sort
+ Chef::Log.debug("#{new_resource.name}: current_members: #{sorted_current_sids} vs new_members #{sorted_members_sids}")
+ sorted_members_sids == sorted_current_sids
+ end
+
+ def group_gid_match?
+ true
end
def load_current_resource
- @current_resource = Chef::Resource::Group.new(@new_resource.name)
- @current_resource.group_name(@new_resource.group_name)
+ @current_resource = Chef::Resource::Group.new(new_resource.name)
+ current_resource.group_name(new_resource.group_name)
members = nil
begin
members = @net_group.local_get_members
- rescue => e
+ rescue
@group_exists = false
- Chef::Log.debug("#{@new_resource} group does not exist")
+ logger.trace("#{new_resource} group does not exist")
end
if members
- @current_resource.members(members)
+ current_resource.members(members)
end
- @current_resource
+ current_resource
end
def create_group
@@ -58,10 +69,10 @@ class Chef
end
def manage_group
- if @new_resource.append
+ if new_resource.append
members_to_be_added = [ ]
- @new_resource.members.each do |member|
- members_to_be_added << member if ! has_current_group_member?(member) && validate_member!(member)
+ new_resource.members.each do |member|
+ members_to_be_added << member if !has_current_group_member?(member) && validate_member!(member)
end
# local_add_members will raise ERROR_MEMBER_IN_ALIAS if a
@@ -69,19 +80,20 @@ class Chef
@net_group.local_add_members(members_to_be_added) unless members_to_be_added.empty?
members_to_be_removed = [ ]
- @new_resource.excluded_members.each do |member|
- member_sid = lookup_account_name(member)
+ new_resource.excluded_members.each do |member|
+ lookup_account_name(member)
members_to_be_removed << member if has_current_group_member?(member)
end
@net_group.local_delete_members(members_to_be_removed) unless members_to_be_removed.empty?
- else
- @net_group.local_set_members(@new_resource.members)
+ elsif !group_members_match?
+ @net_group.local_set_members(new_resource.members)
end
+ @net_group.local_group_set_info(new_resource.comment) if new_resource.comment
end
def has_current_group_member?(member)
member_sid = lookup_account_name(member)
- @current_resource.members.include?(member_sid)
+ current_resource.members.include?(member_sid)
end
def remove_group
@@ -89,7 +101,7 @@ class Chef
end
def locally_qualified_name(account_name)
- account_name.include?("\\") ? account_name : "#{ENV['COMPUTERNAME']}\\#{account_name}"
+ account_name.include?("\\") ? account_name : "#{ENV["COMPUTERNAME"]}\\#{account_name}"
end
def validate_member!(member)
@@ -97,12 +109,10 @@ class Chef
end
def lookup_account_name(account_name)
- begin
- Chef::ReservedNames::Win32::Security.lookup_account_name(locally_qualified_name(account_name))[1].to_s
- rescue Chef::Exceptions::Win32APIError
- Chef::Log.warn("SID for '#{locally_qualified_name(account_name)}' could not be found")
- ""
- end
+ Chef::ReservedNames::Win32::Security.lookup_account_name(locally_qualified_name(account_name))[1].to_s
+ rescue Chef::Exceptions::Win32APIError
+ logger.warn("SID for '#{locally_qualified_name(account_name)}' could not be found")
+ ""
end
end
diff --git a/lib/chef/provider/http_request.rb b/lib/chef/provider/http_request.rb
index e1ee01d9b4..8e7a7f1fc9 100644
--- a/lib/chef/provider/http_request.rb
+++ b/lib/chef/provider/http_request.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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 "tempfile"
-require "chef/http/simple"
+require "tempfile" unless defined?(Tempfile)
+require_relative "../http/simple"
class Chef
class Provider
@@ -27,90 +27,100 @@ class Chef
attr_accessor :http
- def whyrun_supported?
- true
- end
-
def load_current_resource
- @http = Chef::HTTP::Simple.new(@new_resource.url)
+ @http = Chef::HTTP::Simple.new(new_resource.url)
end
- # Send a HEAD request to @new_resource.url
- def action_head
- message = check_message(@new_resource.message)
+ # Send a HEAD request to new_resource.url
+ action :head do
+ message = check_message(new_resource.message)
# CHEF-4762: we expect a nil return value from Chef::HTTP for a "200 Success" response
# and false for a "304 Not Modified" response
modified = @http.head(
- "#{@new_resource.url}",
- @new_resource.headers
+ (new_resource.url).to_s,
+ new_resource.headers
)
- Chef::Log.info("#{@new_resource} HEAD to #{@new_resource.url} successful")
- Chef::Log.debug("#{@new_resource} HEAD request response: #{modified}")
+ logger.info("#{new_resource} HEAD to #{new_resource.url} successful")
+ logger.trace("#{new_resource} HEAD request response: #{modified}")
# :head is usually used to trigger notifications, which converge_by now does
if modified != false
- converge_by("#{@new_resource} HEAD to #{@new_resource.url} returned modified, trigger notifications") {}
+ converge_by("#{new_resource} HEAD to #{new_resource.url} returned modified, trigger notifications") {}
end
end
- # Send a GET request to @new_resource.url
- def action_get
- converge_by("#{@new_resource} GET to #{@new_resource.url}") do
+ # Send a GET request to new_resource.url
+ action :get do
+ converge_by("#{new_resource} GET to #{new_resource.url}") do
- message = check_message(@new_resource.message)
+ message = check_message(new_resource.message)
body = @http.get(
- "#{@new_resource.url}",
- @new_resource.headers
+ (new_resource.url).to_s,
+ new_resource.headers
+ )
+ logger.info("#{new_resource} GET to #{new_resource.url} successful")
+ logger.trace("#{new_resource} GET request response: #{body}")
+ end
+ end
+
+ # Send a PATCH request to new_resource.url, with the message as the payload
+ action :patch do
+ converge_by("#{new_resource} PATCH to #{new_resource.url}") do
+ message = check_message(new_resource.message)
+ body = @http.patch(
+ (new_resource.url).to_s,
+ message,
+ new_resource.headers
)
- Chef::Log.info("#{@new_resource} GET to #{@new_resource.url} successful")
- Chef::Log.debug("#{@new_resource} GET request response: #{body}")
+ logger.info("#{new_resource} PATCH to #{new_resource.url} successful")
+ logger.trace("#{new_resource} PATCH request response: #{body}")
end
end
- # Send a PUT request to @new_resource.url, with the message as the payload
- def action_put
- converge_by("#{@new_resource} PUT to #{@new_resource.url}") do
- message = check_message(@new_resource.message)
+ # Send a PUT request to new_resource.url, with the message as the payload
+ action :put do
+ converge_by("#{new_resource} PUT to #{new_resource.url}") do
+ message = check_message(new_resource.message)
body = @http.put(
- "#{@new_resource.url}",
+ (new_resource.url).to_s,
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}")
+ logger.info("#{new_resource} PUT to #{new_resource.url} successful")
+ logger.trace("#{new_resource} PUT request response: #{body}")
end
end
- # Send a POST request to @new_resource.url, with the message as the payload
- def action_post
- converge_by("#{@new_resource} POST to #{@new_resource.url}") do
- message = check_message(@new_resource.message)
+ # Send a POST request to new_resource.url, with the message as the payload
+ action :post do
+ converge_by("#{new_resource} POST to #{new_resource.url}") do
+ message = check_message(new_resource.message)
body = @http.post(
- "#{@new_resource.url}",
+ (new_resource.url).to_s,
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}")
+ logger.info("#{new_resource} POST to #{new_resource.url} message: #{message.inspect} successful")
+ logger.trace("#{new_resource} POST request response: #{body}")
end
end
- # Send a DELETE request to @new_resource.url
- def action_delete
- converge_by("#{@new_resource} DELETE to #{@new_resource.url}") do
+ # Send a DELETE request to new_resource.url
+ action :delete do
+ converge_by("#{new_resource} DELETE to #{new_resource.url}") do
body = @http.delete(
- "#{@new_resource.url}",
- @new_resource.headers
+ (new_resource.url).to_s,
+ new_resource.headers
)
- @new_resource.updated_by_last_action(true)
- Chef::Log.info("#{@new_resource} DELETE to #{@new_resource.url} successful")
- Chef::Log.debug("#{@new_resource} DELETE request response: #{body}")
+ new_resource.updated_by_last_action(true)
+ logger.info("#{new_resource} DELETE to #{new_resource.url} successful")
+ logger.trace("#{new_resource} DELETE request response: #{body}")
end
end
private
def check_message(message)
- if message.kind_of?(Proc)
+ if message.is_a?(Proc)
message.call
else
message
diff --git a/lib/chef/provider/ifconfig.rb b/lib/chef/provider/ifconfig.rb
index 4cfb257bb9..d08564e75d 100644
--- a/lib/chef/provider/ifconfig.rb
+++ b/lib/chef/provider/ifconfig.rb
@@ -16,80 +16,146 @@
# 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"
-
-# Recipe example:
-#
-# int = {Hash with your network settings...}
-#
-# ifconfig int['ip'] do
-# ignore_failure true
-# device int['dev']
-# mask int['mask']
-# gateway int['gateway']
-# mtu int['mtu']
-# end
+require_relative "../log"
+require_relative "../provider"
+require_relative "../resource/file"
+require_relative "../exceptions"
+autoload :ERB, "erb"
class Chef
class Provider
+ # use the ifconfig resource to manage interfaces on *nix systems
+ #
+ # @example set a static ip on eth1
+ # ifconfig '33.33.33.80' do
+ # device 'eth1'
+ # end
class Ifconfig < Chef::Provider
provides :ifconfig
- include Chef::Mixin::ShellOut
- include Chef::Mixin::Command
-
attr_accessor :config_template
attr_accessor :config_path
+ # @api private
+ # @return [String] the major.minor of the net-tools version as a string
+ attr_accessor :ifconfig_version
+
def initialize(new_resource, run_context)
super(new_resource, run_context)
@config_template = nil
@config_path = nil
end
- def whyrun_supported?
- true
- end
-
def load_current_resource
- @current_resource = Chef::Resource::Ifconfig.new(@new_resource.name)
+ @current_resource = Chef::Resource::Ifconfig.new(new_resource.name)
@ifconfig_success = true
@interfaces = {}
- @status = shell_out("ifconfig")
- @status.stdout.each_line do |line|
- if !line[0..9].strip.empty?
- @int_name = line[0..9].strip
- @interfaces[@int_name] = { "hwaddr" => (line =~ /(HWaddr)/ ? ($') : "nil").strip.chomp }
- else
- @interfaces[@int_name]["inet_addr"] = (line =~ /inet addr:(\S+)/ ? ($1) : "nil") if line =~ /inet addr:/
- @interfaces[@int_name]["bcast"] = (line =~ /Bcast:(\S+)/ ? ($1) : "nil") if line =~ /Bcast:/
- @interfaces[@int_name]["mask"] = (line =~ /Mask:(\S+)/ ? ($1) : "nil") if line =~ /Mask:/
- @interfaces[@int_name]["mtu"] = (line =~ /MTU:(\S+)/ ? ($1) : "nil") if line =~ /MTU:/
- @interfaces[@int_name]["metric"] = (line =~ /Metric:(\S+)/ ? ($1) : "nil") if line =~ /Metric:/
+ @ifconfig_version = nil
+
+ @net_tools_version = shell_out("ifconfig", "--version")
+ @net_tools_version.stdout.each_line do |line|
+ if /^net-tools (\d+\.\d+)/.match?(line)
+ @ifconfig_version = line.match(/^net-tools (\d+\.\d+)/)[1]
end
+ end
+ @net_tools_version.stderr.each_line do |line|
+ if /^net-tools (\d+\.\d+)/.match?(line)
+ @ifconfig_version = line.match(/^net-tools (\d+\.\d+)/)[1]
+ end
+ end
+
+ if @ifconfig_version.nil?
+ raise "net-tools not found - this is required for ifconfig"
+ elsif @ifconfig_version.to_i < 2
+ # Example output for 1.60 is as follows: (sanitized but format intact)
+ # eth0 Link encap:Ethernet HWaddr 00:00:00:00:00:00
+ # inet addr:192.168.1.1 Bcast:192.168.0.1 Mask:255.255.248.0
+ # inet6 addr: 0000::00:0000:0000:0000/64 Scope:Link
+ # UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
+ # RX packets:65158911 errors:0 dropped:0 overruns:0 frame:0
+ # TX packets:41723949 errors:0 dropped:0 overruns:0 carrier:0
+ # collisions:0 txqueuelen:1000
+ # RX bytes:42664658792 (39.7 GiB) TX bytes:52722603938 (49.1 GiB)
+ # Interrupt:30
+ @status = shell_out("ifconfig")
+ @status.stdout.each_line do |line|
+ if !line[0..9].strip.empty?
+ @int_name = line[0..9].strip
+ @interfaces[@int_name] = { "hwaddr" => (line =~ /(HWaddr)/ ? ($') : "nil").strip.chomp }
+ else
+ @interfaces[@int_name]["inet_addr"] = (line =~ /inet addr:(\S+)/ ? Regexp.last_match(1) : "nil") if /inet addr:/.match?(line)
+ @interfaces[@int_name]["bcast"] = (line =~ /Bcast:(\S+)/ ? Regexp.last_match(1) : "nil") if /Bcast:/.match?(line)
+ @interfaces[@int_name]["mask"] = (line =~ /Mask:(\S+)/ ? Regexp.last_match(1) : "nil") if /Mask:/.match?(line)
+ @interfaces[@int_name]["mtu"] = (line =~ /MTU:(\S+)/ ? Regexp.last_match(1) : "nil") if /MTU:/.match?(line)
+ @interfaces[@int_name]["metric"] = (line =~ /Metric:(\S+)/ ? Regexp.last_match(1) : "nil") if /Metric:/.match?(line)
+ end
+
+ next unless @interfaces.key?(new_resource.device)
- if @interfaces.has_key?(@new_resource.device)
- @interface = @interfaces.fetch(@new_resource.device)
-
- @current_resource.target(@new_resource.target)
- @current_resource.device(@new_resource.device)
- @current_resource.inet_addr(@interface["inet_addr"])
- @current_resource.hwaddr(@interface["hwaddr"])
- @current_resource.bcast(@interface["bcast"])
- @current_resource.mask(@interface["mask"])
- @current_resource.mtu(@interface["mtu"])
- @current_resource.metric(@interface["metric"])
+ @interface = @interfaces.fetch(new_resource.device)
+
+ current_resource.target(new_resource.target)
+ current_resource.device(new_resource.device)
+ current_resource.inet_addr(@interface["inet_addr"])
+ current_resource.hwaddr(@interface["hwaddr"])
+ current_resource.bcast(@interface["bcast"])
+ current_resource.mask(@interface["mask"])
+ current_resource.mtu(@interface["mtu"])
+ current_resource.metric(@interface["metric"])
+ end
+ elsif @ifconfig_version.to_i >= 2
+ # Example output for 2.10-alpha is as follows: (sanitized but format intact)
+ # eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
+ # inet 192.168.1.1 netmask 255.255.240.0 broadcast 192.168.0.1
+ # inet6 0000::0000:000:0000:0000 prefixlen 64 scopeid 0x20<link>
+ # ether 00:00:00:00:00:00 txqueuelen 1000 (Ethernet)
+ # RX packets 2383836 bytes 1642630840 (1.5 GiB)
+ # RX errors 0 dropped 0 overruns 0 frame 0
+ # TX packets 1244218 bytes 977339327 (932.0 MiB)
+ # TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
+ #
+ # Permalink for addr_regex : https://rubular.com/r/JrykUpfjRnYeQD
+ @status = shell_out("ifconfig")
+ @status.stdout.each_line do |line|
+ addr_regex = /^((\w|-)+):?(\d*):?\ .+$/
+ if line =~ addr_regex
+ if line.match(addr_regex).nil?
+ @int_name = "nil"
+ elsif line.match(addr_regex)[3] == ""
+ @int_name = line.match(addr_regex)[1]
+ @interfaces[@int_name] = {}
+ @interfaces[@int_name]["mtu"] = (line =~ /mtu (\S+)/ ? Regexp.last_match(1) : "nil") if line.include?("mtu") && @interfaces[@int_name]["mtu"].nil?
+ else
+ @int_name = "#{line.match(addr_regex)[1]}:#{line.match(addr_regex)[3]}"
+ @interfaces[@int_name] = {}
+ @interfaces[@int_name]["mtu"] = (line =~ /mtu (\S+)/ ? Regexp.last_match(1) : "nil") if line.include?("mtu") && @interfaces[@int_name]["mtu"].nil?
+ end
+ else
+ @interfaces[@int_name]["inet_addr"] = (line =~ /inet (\S+)/ ? Regexp.last_match(1) : "nil") if line.include?("inet") && @interfaces[@int_name]["inet_addr"].nil?
+ @interfaces[@int_name]["bcast"] = (line =~ /broadcast (\S+)/ ? Regexp.last_match(1) : "nil") if line.include?("broadcast") && @interfaces[@int_name]["bcast"].nil?
+ @interfaces[@int_name]["mask"] = (line =~ /netmask (\S+)/ ? Regexp.last_match(1) : "nil") if line.include?("netmask") && @interfaces[@int_name]["mask"].nil?
+ @interfaces[@int_name]["hwaddr"] = (line =~ /ether (\S+)/ ? Regexp.last_match(1) : "nil") if line.include?("ether") && @interfaces[@int_name]["hwaddr"].nil?
+ @interfaces[@int_name]["metric"] = (line =~ /Metric:(\S+)/ ? Regexp.last_match(1) : "nil") if line.include?("Metric:") && @interfaces[@int_name]["metric"].nil?
+ end
+
+ next unless @interfaces.key?(new_resource.device)
+
+ @interface = @interfaces.fetch(new_resource.device)
+
+ current_resource.target(new_resource.target)
+ current_resource.device(new_resource.device)
+ current_resource.inet_addr(@interface["inet_addr"])
+ current_resource.hwaddr(@interface["hwaddr"])
+ current_resource.bcast(@interface["bcast"])
+ current_resource.mask(@interface["mask"])
+ current_resource.mtu(@interface["mtu"])
+ current_resource.metric(@interface["metric"])
end
end
- @current_resource
+
+ current_resource
end
def define_resource_requirements
@@ -102,16 +168,14 @@ class Chef
end
end
- def action_add
+ action :add do
# check to see if load_current_resource found interface in ifconfig
- unless @current_resource.inet_addr
- unless @new_resource.device == loopback_device
+ unless current_resource.inet_addr
+ unless new_resource.device == loopback_device
command = add_command
- converge_by ("run #{command} to add #{@new_resource}") do
- run_command(
- :command => command
- )
- Chef::Log.info("#{@new_resource} added")
+ converge_by("run #{command.join(" ")} to add #{new_resource}") do
+ shell_out!(command)
+ logger.info("#{new_resource} added")
end
end
end
@@ -119,56 +183,49 @@ class Chef
generate_config
end
- def action_enable
+ action :enable do
# check to see if load_current_resource found ifconfig
# enables, but does not manage config files
- unless @current_resource.inet_addr
- unless @new_resource.device == loopback_device
- command = enable_command
- converge_by ("run #{command} to enable #{@new_resource}") do
- run_command(
- :command => command
- )
- Chef::Log.info("#{@new_resource} enabled")
- end
- end
+ return if current_resource.inet_addr
+ return if new_resource.device == loopback_device
+
+ command = enable_command
+ converge_by("run #{command.join(" ")} to enable #{new_resource}") do
+ shell_out!(command)
+ logger.info("#{new_resource} enabled")
end
end
- def action_delete
+ action :delete do
# check to see if load_current_resource found the interface
- if @current_resource.device
+ if current_resource.device
command = delete_command
- converge_by ("run #{command} to delete #{@new_resource}") do
- run_command(
- :command => command
- )
- Chef::Log.info("#{@new_resource} deleted")
+ converge_by("run #{command.join(" ")} to delete #{new_resource}") do
+ shell_out!(command)
+ logger.info("#{new_resource} deleted")
end
else
- Chef::Log.debug("#{@new_resource} does not exist - nothing to do")
+ logger.trace("#{new_resource} does not exist - nothing to do")
end
delete_config
end
- def action_disable
+ action :disable do
# check to see if load_current_resource found the interface
# disables, but leaves config files in place.
- if @current_resource.device
+ if current_resource.device
command = disable_command
- converge_by ("run #{command} to disable #{@new_resource}") do
- run_command(
- :command => command
- )
- Chef::Log.info("#{@new_resource} disabled")
+ converge_by("run #{command.join(" ")} to disable #{new_resource}") do
+ shell_out!(command)
+ logger.info("#{new_resource} disabled")
end
else
- Chef::Log.debug("#{@new_resource} does not exist - nothing to do")
+ logger.trace("#{new_resource} does not exist - nothing to do")
end
end
def can_generate_config?
- ! @config_template.nil? && ! @config_path.nil?
+ !@config_template.nil? && !@config_path.nil?
end
def resource_for_config(path)
@@ -177,45 +234,47 @@ class Chef
def generate_config
return unless can_generate_config?
+
b = binding
- template = ::ERB.new(@config_template)
+ template = ::ERB.new(@config_template, nil, "-")
config = resource_for_config(@config_path)
config.content(template.result(b))
config.run_action(:create)
- @new_resource.updated_by_last_action(true) if config.updated?
+ new_resource.updated_by_last_action(true) if config.updated?
end
def delete_config
return unless can_generate_config?
+
config = resource_for_config(@config_path)
config.run_action(:delete)
- @new_resource.updated_by_last_action(true) if config.updated?
+ new_resource.updated_by_last_action(true) if config.updated?
end
private
def add_command
- 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
+ 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
command
end
def enable_command
- 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
+ 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
command
end
def disable_command
- "ifconfig #{@new_resource.device} down"
+ [ "ifconfig", new_resource.device, "down" ]
end
def delete_command
- "ifconfig #{@new_resource.device} down"
+ [ "ifconfig", new_resource.device, "down" ]
end
def loopback_device
diff --git a/lib/chef/provider/ifconfig/aix.rb b/lib/chef/provider/ifconfig/aix.rb
index 81164db304..16e7ad0a8b 100644
--- a/lib/chef/provider/ifconfig/aix.rb
+++ b/lib/chef/provider/ifconfig/aix.rb
@@ -1,6 +1,6 @@
#
# Author:: Kaustubh Deorukhkar (kaustubh@clogeny.com)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,70 +16,66 @@
# limitations under the License.
#
-require "chef/provider/ifconfig"
+require_relative "../ifconfig"
class Chef
class Provider
class Ifconfig
class Aix < Chef::Provider::Ifconfig
- provides :ifconfig, platform: %w{aix}
+ provides :ifconfig, platform: "aix"
def load_current_resource
- @current_resource = Chef::Resource::Ifconfig.new(@new_resource.name)
+ @current_resource = Chef::Resource::Ifconfig.new(new_resource.name)
@interface_exists = false
found_interface = false
interface = {}
- @status = shell_out("ifconfig -a")
+ @status = shell_out("ifconfig", "-a")
@status.stdout.each_line do |line|
if !found_interface
if line =~ /^(\S+):\sflags=(\S+)/
- # We have interface name, if this is the interface for @current_resource, load info else skip till next interface is found.
- if $1 == @new_resource.device
+ # We have interface name, if this is the interface for current_resource, load info else skip till next interface is found.
+ if Regexp.last_match(1) == new_resource.device
# Found interface
found_interface = true
@interface_exists = true
- @current_resource.target(@new_resource.target)
- @current_resource.device($1)
- interface[:flags] = $2
- @current_resource.metric($1) if line =~ /metric\s(\S+)/
- end
- end
- else
- # parse interface related information, stop when next interface is found.
- if line =~ /^(\S+):\sflags=(\S+)/
- # we are done parsing interface info and hit another one, so stop.
- found_interface = false
- break
- else
- if found_interface
- # read up interface info
- @current_resource.inet_addr($1) if line =~ /inet\s(\S+)\s/
- @current_resource.bcast($1) if line =~ /broadcast\s(\S+)/
- @current_resource.mask(hex_to_dec_netmask($1)) if line =~ /netmask\s(\S+)\s/
+ current_resource.target(new_resource.target)
+ current_resource.device(Regexp.last_match(1))
+ interface[:flags] = Regexp.last_match(2)
+ current_resource.metric(Regexp.last_match(1)) if line =~ /metric\s(\S+)/
end
end
+ elsif line =~ /^(\S+):\sflags=(\S+)/
+ # we are done parsing interface info and hit another one, so stop.
+ found_interface = false
+ break
+ elsif found_interface
+ # read up interface info
+ current_resource.inet_addr(Regexp.last_match(1)) if line =~ /inet\s(\S+)\s/
+ current_resource.bcast(Regexp.last_match(1)) if line =~ /broadcast\s(\S+)/
+ current_resource.mask(hex_to_dec_netmask(Regexp.last_match(1))) if line =~ /netmask\s(\S+)\s/
end
end
- @current_resource
+ current_resource
end
private
def add_command
# ifconfig changes are temporary, chdev persist across reboots.
- raise Chef::Exceptions::Ifconfig, "interface metric attribute cannot be set for :add action" if @new_resource.metric
- command = "chdev -l #{@new_resource.device} -a netaddr=#{@new_resource.name}"
- command << " -a netmask=#{@new_resource.mask}" if @new_resource.mask
- command << " -a mtu=#{@new_resource.mtu}" if @new_resource.mtu
+ raise Chef::Exceptions::Ifconfig, "interface metric property cannot be set for :add action" if new_resource.metric
+
+ command = [ "chdev", "-l", new_resource.device, "-a", "netaddr=#{new_resource.name}" ]
+ command += [ "-a", "netmask=#{new_resource.mask}" ] if new_resource.mask
+ command += [ "-a", "mtu=#{new_resource.mtu}" ] if new_resource.mtu
command
end
def delete_command
# ifconfig changes are temporary, chdev persist across reboots.
- "chdev -l #{@new_resource.device} -a state=down"
+ [ "chdev", "-l", new_resource.device, "-a", "state=down" ]
end
def loopback_device
diff --git a/lib/chef/provider/ifconfig/debian.rb b/lib/chef/provider/ifconfig/debian.rb
index 872b0db152..9b359d7c54 100644
--- a/lib/chef/provider/ifconfig/debian.rb
+++ b/lib/chef/provider/ifconfig/debian.rb
@@ -16,42 +16,60 @@
# limitations under the License.
#
-require "chef/provider/ifconfig"
-require "chef/util/file_edit"
+require_relative "../ifconfig"
+require_relative "../../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"
+ provides :ifconfig, platform_family: %w{debian}
- INTERFACES_FILE = "/etc/network/interfaces"
- INTERFACES_DOT_D_DIR = "/etc/network/interfaces.d"
+ INTERFACES_FILE = "/etc/network/interfaces".freeze
+ INTERFACES_DOT_D_DIR = "/etc/network/interfaces.d".freeze
def initialize(new_resource, run_context)
super(new_resource, run_context)
@config_template = %{
-<% if @new_resource.device %>
-<% if @new_resource.onboot == "yes" %>auto <%= @new_resource.device %><% end %>
-<% case @new_resource.bootproto
- when "dhcp" %>
-iface <%= @new_resource.device %> inet dhcp
-<% when "bootp" %>
-iface <%= @new_resource.device %> inet bootp
-<% else %>
-iface <%= @new_resource.device %> inet static
- <% if @new_resource.target %>address <%= @new_resource.target %><% end %>
- <% if @new_resource.mask %>netmask <%= @new_resource.mask %><% end %>
- <% if @new_resource.network %>network <%= @new_resource.network %><% end %>
- <% if @new_resource.bcast %>broadcast <%= @new_resource.bcast %><% end %>
- <% if @new_resource.metric %>metric <%= @new_resource.metric %><% end %>
- <% if @new_resource.hwaddr %>hwaddress <%= @new_resource.hwaddr %><% end %>
- <% if @new_resource.mtu %>mtu <%= @new_resource.mtu %><% end %>
-<% end %>
-<% end %>
+<% if new_resource.device -%>
+<% if new_resource.onboot == "yes" -%>
+auto <%= new_resource.device %>
+<% end -%>
+<% case new_resource.bootproto
+ when "dhcp" -%>
+iface <%= new_resource.device %> <%= new_resource.family %> dhcp
+<% when "bootp" -%>
+iface <%= new_resource.device %> <%= new_resource.family %> bootp
+<% else -%>
+iface <%= new_resource.device %> <%= new_resource.family %> static
+ <% if new_resource.target -%>
+ address <%= new_resource.target %>
+ <% end -%>
+ <% if new_resource.mask -%>
+ netmask <%= new_resource.mask %>
+ <% end -%>
+ <% if new_resource.network -%>
+ network <%= new_resource.network %>
+ <% end -%>
+ <% if new_resource.bcast -%>
+ broadcast <%= new_resource.bcast %>
+ <% end -%>
+ <% if new_resource.metric -%>
+ metric <%= new_resource.metric %>
+ <% end -%>
+ <% if new_resource.hwaddr -%>
+ hwaddress <%= new_resource.hwaddr %>
+ <% end -%>
+ <% if new_resource.mtu -%>
+ mtu <%= new_resource.mtu %>
+ <% end -%>
+ <% if new_resource.gateway -%>
+ gateway <%= new_resource.gateway %>
+ <% end -%>
+<% end -%>
+<% end -%>
}
- @config_path = "#{INTERFACES_DOT_D_DIR}/ifcfg-#{@new_resource.device}"
+ @config_path = "#{INTERFACES_DOT_D_DIR}/ifcfg-#{new_resource.device}"
end
def generate_config
@@ -62,19 +80,22 @@ iface <%= @new_resource.device %> inet static
protected
def enforce_interfaces_dot_d_sanity
- # create /etc/network/interfaces.d via dir resource (to get reporting, etc)
- dir = Chef::Resource::Directory.new(INTERFACES_DOT_D_DIR, run_context)
- dir.run_action(:create)
- new_resource.updated_by_last_action(true) if dir.updated_by_last_action?
+ # on ubuntu 18.04+ there's no interfaces file and it uses interfaces.d by default
+ return if ::File.directory?(INTERFACES_DOT_D_DIR) && !::File.exist?(INTERFACES_FILE)
+
+ # create /etc/network/interfaces.d via dir if it's missing
+ directory INTERFACES_DOT_D_DIR
+
# roll our own file_edit resource, this will not get reported until we have a file_edit resource
interfaces_dot_d_for_regexp = INTERFACES_DOT_D_DIR.gsub(/\./, '\.') # escape dots for the regexp
regexp = %r{^\s*source\s+#{interfaces_dot_d_for_regexp}/\*\s*$}
- unless ::File.exists?(INTERFACES_FILE) && regexp.match(IO.read(INTERFACES_FILE))
- converge_by("modifying #{INTERFACES_FILE} to source #{INTERFACES_DOT_D_DIR}") do
- conf = Chef::Util::FileEdit.new(INTERFACES_FILE)
- conf.insert_line_if_no_match(regexp, "source #{INTERFACES_DOT_D_DIR}/*")
- conf.write_file
- end
+
+ return if ::File.exist?(INTERFACES_FILE) && regexp.match(IO.read(INTERFACES_FILE))
+
+ converge_by("modifying #{INTERFACES_FILE} to source #{INTERFACES_DOT_D_DIR}") do
+ conf = Chef::Util::FileEdit.new(INTERFACES_FILE)
+ conf.insert_line_if_no_match(regexp, "source #{INTERFACES_DOT_D_DIR}/*")
+ conf.write_file
end
end
diff --git a/lib/chef/provider/ifconfig/redhat.rb b/lib/chef/provider/ifconfig/redhat.rb
index 0c28e6407a..a7f73c43dc 100644
--- a/lib/chef/provider/ifconfig/redhat.rb
+++ b/lib/chef/provider/ifconfig/redhat.rb
@@ -16,32 +16,74 @@
# limitations under the License.
#
-require "chef/provider/ifconfig"
+require_relative "../ifconfig"
class Chef
class Provider
class Ifconfig
class Redhat < Chef::Provider::Ifconfig
- provides :ifconfig, platform_family: %w{fedora rhel}
+ provides :ifconfig, platform_family: "fedora_derived"
def initialize(new_resource, run_context)
super(new_resource, run_context)
@config_template = %{
-<% if @new_resource.device %>DEVICE=<%= @new_resource.device %><% end %>
-<% if @new_resource.onboot == "yes" %>ONBOOT=<%= @new_resource.onboot %><% end %>
-<% if @new_resource.bootproto %>BOOTPROTO=<%= @new_resource.bootproto %><% end %>
-<% if @new_resource.target %>IPADDR=<%= @new_resource.target %><% end %>
-<% if @new_resource.mask %>NETMASK=<%= @new_resource.mask %><% end %>
-<% if @new_resource.network %>NETWORK=<%= @new_resource.network %><% end %>
-<% if @new_resource.bcast %>BROADCAST=<%= @new_resource.bcast %><% end %>
-<% if @new_resource.onparent %>ONPARENT=<%= @new_resource.onparent %><% end %>
-<% if @new_resource.hwaddr %>HWADDR=<%= @new_resource.hwaddr %><% end %>
-<% if @new_resource.metric %>METRIC=<%= @new_resource.metric %><% end %>
-<% if @new_resource.mtu %>MTU=<%= @new_resource.mtu %><% end %>
+<% if new_resource.device -%>
+DEVICE=<%= new_resource.device %>
+<% end -%>
+<% if new_resource.onboot == "yes" -%>
+ONBOOT=<%= new_resource.onboot %>
+<% end -%>
+<% if new_resource.bootproto -%>
+BOOTPROTO=<%= new_resource.bootproto %>
+<% end -%>
+<% if new_resource.target -%>
+IPADDR=<%= new_resource.target %>
+<% end -%>
+<% if new_resource.mask -%>
+NETMASK=<%= new_resource.mask %>
+<% end -%>
+<% if new_resource.network -%>
+NETWORK=<%= new_resource.network %>
+<% end -%>
+<% if new_resource.bcast -%>
+BROADCAST=<%= new_resource.bcast %>
+<% end -%>
+<% if new_resource.onparent -%>
+ONPARENT=<%= new_resource.onparent %>
+<% end -%>
+<% if new_resource.hwaddr -%>
+HWADDR=<%= new_resource.hwaddr %>
+<% end -%>
+<% if new_resource.metric -%>
+METRIC=<%= new_resource.metric %>
+<% end -%>
+<% if new_resource.mtu -%>
+MTU=<%= new_resource.mtu %>
+<% end -%>
+<% if new_resource.ethtool_opts -%>
+ETHTOOL_OPTS="<%= new_resource.ethtool_opts %>"
+<% end -%>
+<% if new_resource.bonding_opts -%>
+BONDING_OPTS="<%= new_resource.bonding_opts %>"
+<% end -%>
+<% if new_resource.master -%>
+MASTER=<%= new_resource.master %>
+<% end -%>
+<% if new_resource.slave -%>
+SLAVE=<%= new_resource.slave %>
+<% end -%>
+<% if new_resource.vlan -%>
+VLAN=<%= new_resource.vlan %>
+<% end -%>
+<% if new_resource.gateway -%>
+GATEWAY=<%= new_resource.gateway %>
+<% end -%>
+<% if new_resource.bridge -%>
+BRIDGE=<%= new_resource.bridge %>
+<% end -%>
}
- @config_path = "/etc/sysconfig/network-scripts/ifcfg-#{@new_resource.device}"
+ @config_path = "/etc/sysconfig/network-scripts/ifcfg-#{new_resource.device}"
end
-
end
end
end
diff --git a/lib/chef/provider/launchd.rb b/lib/chef/provider/launchd.rb
index c58d4bfa34..b8ff9dfa4d 100644
--- a/lib/chef/provider/launchd.rb
+++ b/lib/chef/provider/launchd.rb
@@ -16,13 +16,12 @@
# limitations under the License.
#
-require "chef/provider"
-require "chef/resource/launchd"
-require "chef/resource/file"
-require "chef/resource/cookbook_file"
-require "chef/resource/macosx_service"
-require "plist"
-require "forwardable"
+require_relative "../provider"
+require_relative "../resource/file"
+require_relative "../resource/cookbook_file"
+require_relative "../resource/macosx_service"
+autoload :Plist, "plist"
+require "forwardable" unless defined?(Forwardable)
class Chef
class Provider
@@ -30,22 +29,10 @@ class Chef
extend Forwardable
provides :launchd, os: "darwin"
- def_delegators :@new_resource, *[
- :backup,
- :cookbook,
- :group,
- :label,
- :mode,
- :owner,
- :path,
- :source,
- :session_type,
- :type,
- ]
+ def_delegators :new_resource, :backup, :cookbook, :group, :label, :mode, :owner, :source, :session_type, :type
def load_current_resource
current_resource = Chef::Resource::Launchd.new(new_resource.name)
- @path = path ? path : gen_path_from_type
end
def gen_path_from_type
@@ -56,82 +43,86 @@ class Chef
types[type]
end
- def action_create
+ action :create do
manage_plist(:create)
end
- def action_create_if_missing
+ action :create_if_missing do
manage_plist(:create_if_missing)
end
- def action_delete
- # If you delete a service you want to make sure its not loaded or
- # the service will be in memory and you wont be able to stop it.
- if ::File.exists?(@path)
+ action :delete do
+ if ::File.exists?(path)
manage_service(:disable)
end
manage_plist(:delete)
end
- def action_enable
- if manage_plist(:create)
- manage_service(:restart)
- else
- manage_service(:enable)
+ action :enable do
+ manage_service(:nothing)
+ manage_plist(:create) do
+ notifies :restart, "macosx_service[#{label}]", :immediately
end
+ manage_service(:enable)
end
- def action_disable
+ action :disable do
+ return unless ::File.exist?(path)
+
manage_service(:disable)
end
- def manage_plist(action)
+ action :restart do
+ manage_service(:restart)
+ end
+
+ def manage_plist(action, &block)
if source
- res = cookbook_file_resource
+ cookbook_file path do
+ cookbook_name = new_resource.cookbook if new_resource.cookbook
+ copy_properties_from(new_resource, :backup, :group, :mode, :owner, :source)
+ action(action)
+ only_if { manage_agent?(action) }
+ instance_eval(&block) if block_given?
+ end
else
- res = file_resource
+ file path do
+ copy_properties_from(new_resource, :backup, :group, :mode, :owner)
+ content(file_content) if file_content?
+ action(action)
+ only_if { manage_agent?(action) }
+ instance_eval(&block) if block_given?
+ end
end
- res.run_action(action)
- new_resource.updated_by_last_action(true) if res.updated?
- res.updated
end
def manage_service(action)
- res = service_resource
- res.run_action(action)
- new_resource.updated_by_last_action(true) if res.updated?
- end
-
- def service_resource
- res = Chef::Resource::MacosxService.new(label, run_context)
- res.name(label) if label
- res.service_name(label) if label
- res.plist(@path) if @path
- res.session_type(session_type) if session_type
- res
- end
-
- def file_resource
- res = Chef::Resource::File.new(@path, run_context)
- res.name(@path) if @path
- res.backup(backup) if backup
- res.content(content) if content
- res.group(group) if group
- res.mode(mode) if mode
- res.owner(owner) if owner
- res
- end
-
- def cookbook_file_resource
- res = Chef::Resource::CookbookFile.new(@path, run_context)
- res.cookbook_name = cookbook if cookbook
- res.name(@path) if @path
- res.backup(backup) if backup
- res.group(group) if group
- res.mode(mode) if mode
- res.owner(owner) if owner
- res.source(source) if source
- res
+ plist_path = path
+ macosx_service label do
+ service_name(new_resource.label) if new_resource.label
+ plist(plist_path) if plist_path
+ copy_properties_from(new_resource, :session_type)
+ action(action)
+ only_if { manage_agent?(action) }
+ end
+ end
+
+ def manage_agent?(action)
+ # Gets UID of console_user and converts to string.
+ console_user = Etc.getpwuid(::File.stat("/dev/console").uid).name
+ root = console_user == "root"
+ agent = type == "agent"
+ invalid_action = %i{delete disable enable restart}.include?(action)
+ lltstype = ""
+ if new_resource.limit_load_to_session_type
+ lltstype = new_resource.limit_load_to_session_type
+ end
+ invalid_type = lltstype != "LoginWindow"
+ if root && agent && invalid_action && invalid_type
+ logger.trace("#{label}: Aqua LaunchAgents shouldn't be loaded as root")
+ return false
+ end
+ true
end
def define_resource_requirements
@@ -145,17 +136,18 @@ class Chef
end
end
- def content?
- !!content
+ def file_content?
+ !!file_content
end
- def content
- plist_hash = new_resource.hash || gen_hash
- Plist::Emit.dump(plist_hash) unless plist_hash.nil?
+ def file_content
+ plist_hash = new_resource.plist_hash || gen_hash
+ ::Plist::Emit.dump(plist_hash) unless plist_hash.nil?
end
def gen_hash
return nil unless new_resource.program || new_resource.program_arguments
+
{
"label" => "Label",
"program" => "Program",
@@ -168,10 +160,11 @@ class Chef
"environment_variables" => "EnvironmentVariables",
"exit_timeout" => "ExitTimeout",
"ld_group" => "GroupName",
- "hard_resource_limits" => "HardreSourceLimits",
+ "hard_resource_limits" => "HardResourceLimits",
"inetd_compatibility" => "inetdCompatibility",
"init_groups" => "InitGroups",
"keep_alive" => "KeepAlive",
+ "launch_events" => "LaunchEvents",
"launch_only_once" => "LaunchOnlyOnce",
"limit_load_from_hosts" => "LimitLoadFromHosts",
"limit_load_to_hosts" => "LimitLoadToHosts",
@@ -203,6 +196,11 @@ class Chef
memo[val] = new_resource.send(key) if new_resource.send(key)
end
end
+
+ # @api private
+ def path
+ @path ||= new_resource.path || gen_path_from_type
+ end
end
end
end
diff --git a/lib/chef/provider/link.rb b/lib/chef/provider/link.rb
index 16d30319b3..900d0516af 100644
--- a/lib/chef/provider/link.rb
+++ b/lib/chef/provider/link.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -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_relative "../config"
+require_relative "../log"
+require_relative "../mixin/file_class"
+require_relative "../resource/link"
+require_relative "../provider"
+require_relative "../scan_access_control"
+require_relative "../util/path_helper"
class Chef
class Provider
@@ -33,104 +33,97 @@ class Chef
include Chef::Mixin::EnforceOwnershipAndPermissions
include Chef::Mixin::FileClass
- def negative_complement(big)
- if big > 1073741823 # Fixnum max
- big -= (2**32) # diminished radix wrap to negative
- end
- big
- end
-
- private :negative_complement
-
- def whyrun_supported?
- true
- end
-
def load_current_resource
- @current_resource = Chef::Resource::Link.new(@new_resource.name)
- @current_resource.target_file(@new_resource.target_file)
- if file_class.symlink?(@current_resource.target_file)
- @current_resource.link_type(:symbolic)
- @current_resource.to(
- canonicalize(file_class.readlink(@current_resource.target_file))
+ @current_resource = Chef::Resource::Link.new(new_resource.name)
+ current_resource.target_file(new_resource.target_file)
+ if file_class.symlink?(current_resource.target_file)
+ current_resource.link_type(:symbolic)
+ current_resource.to(
+ canonicalize(file_class.readlink(current_resource.target_file))
)
else
- @current_resource.link_type(:hard)
- if ::File.exists?(@current_resource.target_file)
- if ::File.exists?(@new_resource.to) &&
- file_class.stat(@current_resource.target_file).ino ==
- file_class.stat(@new_resource.to).ino
- @current_resource.to(canonicalize(@new_resource.to))
+ current_resource.link_type(:hard)
+ if ::File.exists?(current_resource.target_file)
+ if ::File.exists?(new_resource.to) &&
+ file_class.stat(current_resource.target_file).ino ==
+ file_class.stat(new_resource.to).ino
+ current_resource.to(canonicalize(new_resource.to))
else
- @current_resource.to("")
+ current_resource.to("")
end
end
end
- ScanAccessControl.new(@new_resource, @current_resource).set_all!
- @current_resource
+ ScanAccessControl.new(new_resource, current_resource).set_all!
+ current_resource
end
def define_resource_requirements
requirements.assert(:delete) do |a|
a.assertion do
- if @current_resource.to
- @current_resource.link_type == @new_resource.link_type &&
- (@current_resource.link_type == :symbolic || @current_resource.to != "")
+ if current_resource.to
+ current_resource.link_type == new_resource.link_type &&
+ (current_resource.link_type == :symbolic || 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} link."
- a.whyrun("Would assume the link at #{@new_resource.target_file} was previously created")
+ 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.tr("/", '\\') : path
+ ChefUtils.windows? ? path.tr("/", '\\') : path
end
- def action_create
+ action :create do
# current_resource is the symlink that currently exists
# new_resource is the symlink we need to create
# to - the location to link to
# target_file - the name of the link
- if @current_resource.to != canonicalize(@new_resource.to) ||
- @current_resource.link_type != @new_resource.link_type
+ if current_resource.to != canonicalize(new_resource.to) ||
+ current_resource.link_type != new_resource.link_type
# Handle the case where the symlink already exists and is pointing at a valid to_file
- if @current_resource.to
+ if current_resource.to
# On Windows, to fix a symlink already pointing at a directory we must first
# ::Dir.unlink the symlink (not the directory), while if we have a symlink
# pointing at file we must use ::File.unlink on the symlink.
# However if the new symlink will point to a file and the current symlink is pointing at a
# directory we want to throw an exception and calling ::File.unlink on the directory symlink
# will throw the correct ones.
- if Chef::Platform.windows? && ::File.directory?(@new_resource.to) &&
- ::File.directory?(@current_resource.target_file)
- converge_by("unlink existing windows symlink to dir at #{@new_resource.target_file}") do
- ::Dir.unlink(@new_resource.target_file)
+ if ChefUtils.windows? && ::File.directory?(new_resource.to) &&
+ ::File.directory?(current_resource.target_file)
+ converge_by("unlink existing windows symlink to dir at #{new_resource.target_file}") do
+ ::Dir.unlink(new_resource.target_file)
end
else
- converge_by("unlink existing symlink to file at #{@new_resource.target_file}") do
- ::File.unlink(@new_resource.target_file)
+ converge_by("unlink existing symlink to file at #{new_resource.target_file}") do
+ ::File.unlink(new_resource.target_file)
end
end
end
- if @new_resource.link_type == :symbolic
- converge_by("create symlink at #{@new_resource.target_file} to #{@new_resource.to}") do
- file_class.symlink(canonicalize(@new_resource.to), @new_resource.target_file)
- Chef::Log.debug("#{@new_resource} created #{@new_resource.link_type} link from #{@new_resource.target_file} -> #{@new_resource.to}")
- Chef::Log.info("#{@new_resource} created")
+ if new_resource.link_type == :symbolic
+ converge_by("create symlink at #{new_resource.target_file} to #{new_resource.to}") do
+ file_class.symlink(canonicalize(new_resource.to), new_resource.target_file)
+ logger.trace("#{new_resource} created #{new_resource.link_type} link from #{new_resource.target_file} -> #{new_resource.to}")
+ logger.info("#{new_resource} created")
+ # file_class.symlink will create the link with default access controls.
+ # This means that the access controls of the file could be different
+ # than those captured during the initial evaluation of current_resource.
+ # We need to re-evaluate the current_resource to ensure that the desired
+ # access controls are applied.
+ ScanAccessControl.new(new_resource, current_resource).set_all!
end
- elsif @new_resource.link_type == :hard
- converge_by("create hard link at #{@new_resource.target_file} to #{@new_resource.to}") do
- file_class.link(@new_resource.to, @new_resource.target_file)
- Chef::Log.debug("#{@new_resource} created #{@new_resource.link_type} link from #{@new_resource.target_file} -> #{@new_resource.to}")
- Chef::Log.info("#{@new_resource} created")
+ elsif new_resource.link_type == :hard
+ converge_by("create hard link at #{new_resource.target_file} to #{new_resource.to}") do
+ file_class.link(new_resource.to, new_resource.target_file)
+ logger.trace("#{new_resource} created #{new_resource.link_type} link from #{new_resource.target_file} -> #{new_resource.to}")
+ logger.info("#{new_resource} created")
end
end
end
- if @new_resource.link_type == :symbolic
+ if new_resource.link_type == :symbolic
if access_controls.requires_changes?
converge_by(access_controls.describe_changes) do
access_controls.set_all
@@ -139,17 +132,17 @@ class Chef
end
end
- def action_delete
- if @current_resource.to # Exists
- if Chef::Platform.windows? && ::File.directory?(@current_resource.target_file)
- converge_by("delete link to dir at #{@new_resource.target_file}") do
- ::Dir.delete(@new_resource.target_file)
- Chef::Log.info("#{@new_resource} deleted")
+ action :delete do
+ if current_resource.to # Exists
+ if ChefUtils.windows? && ::File.directory?(current_resource.target_file)
+ converge_by("delete link to dir at #{new_resource.target_file}") do
+ ::Dir.delete(new_resource.target_file)
+ logger.info("#{new_resource} deleted")
end
else
- converge_by("delete link to file at #{@new_resource.target_file}") do
- ::File.delete(@new_resource.target_file)
- Chef::Log.info("#{@new_resource} deleted")
+ converge_by("delete link to file at #{new_resource.target_file}") do
+ ::File.delete(new_resource.target_file)
+ logger.info("#{new_resource} deleted")
end
end
end
@@ -159,7 +152,7 @@ class Chef
# access control (e.g., use lchmod instead of chmod) if the resource is a
# symlink.
def manage_symlink_access?
- @new_resource.link_type == :symbolic
+ new_resource.link_type == :symbolic
end
end
end
diff --git a/lib/chef/provider/log.rb b/lib/chef/provider/log.rb
deleted file mode 100644
index 567781cb41..0000000000
--- a/lib/chef/provider/log.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-#
-# Author:: Cary Penniman (<cary@rightscale.com>)
-# Copyright:: Copyright 2008-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.
-#
-
-class Chef
-
- class Provider
-
- class Log
-
- # Chef log provider, allows logging to chef's logs from recipes
- class ChefLog < Chef::Provider
-
- provides :log
-
- def whyrun_supported?
- true
- end
-
- # No concept of a 'current' resource for logs, this is a no-op
- #
- # === Return
- # true:: Always return true
- def load_current_resource
- true
- end
-
- # Write the log to Chef's log
- #
- # === Return
- # true:: Always return true
- def action_write
- Chef::Log.send(@new_resource.level, @new_resource.message)
- @new_resource.updated_by_last_action(true) if Chef::Config[:count_log_resource_updates]
- end
-
- end
-
- end
-
- end
-
-end
diff --git a/lib/chef/provider/lwrp_base.rb b/lib/chef/provider/lwrp_base.rb
index cbf25f1e4f..a95927daf3 100644
--- a/lib/chef/provider/lwrp_base.rb
+++ b/lib/chef/provider/lwrp_base.rb
@@ -2,7 +2,7 @@
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,9 +18,9 @@
# limitations under the License.
#
-require "chef/provider"
-require "chef/dsl/recipe"
-require "chef/dsl/include_recipe"
+require_relative "../provider"
+require_relative "../dsl/recipe"
+require_relative "../dsl/include_recipe"
class Chef
class Provider
@@ -42,8 +42,7 @@ class Chef
# 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
+ def load_current_resource; end
# class methods
class <<self
@@ -52,7 +51,7 @@ class Chef
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.")
+ Chef::Log.trace("LWRP provider #{filename} from cookbook #{cookbook_name} has already been loaded! Skipping the reload.")
return loaded_lwrps[filename]
end
@@ -71,22 +70,13 @@ class Chef
define_singleton_method(:inspect) { to_s }
end
- Chef::Log.debug("Loaded contents of #{filename} into provider #{resource_name} (#{provider_class})")
+ Chef::Log.trace("Loaded contents of #{filename} into provider #{resource_name} (#{provider_class})")
LWRPBase.loaded_lwrps[filename] = true
- Chef::Provider.register_deprecated_lwrp_class(provider_class, convert_to_class_name(resource_name))
-
provider_class
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
diff --git a/lib/chef/provider/mdadm.rb b/lib/chef/provider/mdadm.rb
deleted file mode 100644
index f8225ff63a..0000000000
--- a/lib/chef/provider/mdadm.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-#
-# Author:: Joe Williams (<joe@joetify.com>)
-# Copyright:: Copyright 2009-2016, 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/log"
-require "chef/provider"
-
-class Chef
- class Provider
- class Mdadm < Chef::Provider
-
- provides :mdadm
-
- def popen4
- raise Exception, "deprecated"
- end
-
- def whyrun_supported?
- true
- end
-
- def load_current_resource
- @current_resource = Chef::Resource::Mdadm.new(@new_resource.name)
- @current_resource.raid_device(@new_resource.raid_device)
- Chef::Log.debug("#{@new_resource} checking for software raid device #{@current_resource.raid_device}")
-
- device_not_found = 4
- mdadm = shell_out!("mdadm --detail --test #{@new_resource.raid_device}", :returns => [0, device_not_found])
- exists = (mdadm.status == 0)
- @current_resource.exists(exists)
- end
-
- def action_create
- unless @current_resource.exists
- converge_by("create RAID device #{new_resource.raid_device}") do
- command = "yes | mdadm --create #{@new_resource.raid_device} --level #{@new_resource.level}"
- command << " --chunk=#{@new_resource.chunk}" unless @new_resource.level == 1
- command << " --metadata=#{@new_resource.metadata}"
- command << " --bitmap=#{@new_resource.bitmap}" if @new_resource.bitmap
- command << " --layout=#{@new_resource.layout}" if @new_resource.layout
- command << " --raid-devices #{@new_resource.devices.length} #{@new_resource.devices.join(" ")}"
- Chef::Log.debug("#{@new_resource} mdadm command: #{command}")
- shell_out!(command)
- Chef::Log.info("#{@new_resource} created raid device (#{@new_resource.raid_device})")
- end
- else
- Chef::Log.debug("#{@new_resource} raid device already exists, skipping create (#{@new_resource.raid_device})")
- end
- end
-
- def action_assemble
- unless @current_resource.exists
- converge_by("assemble RAID device #{new_resource.raid_device}") do
- command = "yes | mdadm --assemble #{@new_resource.raid_device} #{@new_resource.devices.join(" ")}"
- Chef::Log.debug("#{@new_resource} mdadm command: #{command}")
- shell_out!(command)
- Chef::Log.info("#{@new_resource} assembled raid device (#{@new_resource.raid_device})")
- end
- else
- Chef::Log.debug("#{@new_resource} raid device already exists, skipping assemble (#{@new_resource.raid_device})")
- end
- end
-
- def action_stop
- if @current_resource.exists
- converge_by("stop RAID device #{new_resource.raid_device}") do
- command = "yes | mdadm --stop #{@new_resource.raid_device}"
- Chef::Log.debug("#{@new_resource} mdadm command: #{command}")
- shell_out!(command)
- Chef::Log.info("#{@new_resource} stopped raid device (#{@new_resource.raid_device})")
- end
- else
- Chef::Log.debug("#{@new_resource} raid device doesn't exist (#{@new_resource.raid_device}) - not stopping")
- end
- end
-
- end
- end
-end
diff --git a/lib/chef/provider/mount.rb b/lib/chef/provider/mount.rb
index 9e9ee29bde..44fb94ca01 100644
--- a/lib/chef/provider/mount.rb
+++ b/lib/chef/provider/mount.rb
@@ -1,7 +1,7 @@
#
# Author:: Joshua Timberman (<joshua@chef.io>)
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,21 +17,15 @@
# limitations under the License.
#
-require "chef/log"
-require "chef/mixin/shell_out"
-require "chef/provider"
+require_relative "../log"
+require_relative "../provider"
class Chef
class Provider
class Mount < Chef::Provider
- include Chef::Mixin::ShellOut
attr_accessor :unmount_retries
- def whyrun_supported?
- true
- end
-
def load_current_resource
true
end
@@ -41,73 +35,75 @@ class Chef
self.unmount_retries = 20
end
- def action_mount
+ action :mount do
unless current_resource.mounted
converge_by("mount #{current_resource.device} to #{current_resource.mount_point}") do
mount_fs
- Chef::Log.info("#{new_resource} mounted")
+ logger.info("#{new_resource} mounted")
end
else
- Chef::Log.debug("#{new_resource} is already mounted")
+ logger.trace("#{new_resource} is already mounted")
end
end
- def action_umount
+ action :umount do
if current_resource.mounted
converge_by("unmount #{current_resource.device}") do
umount_fs
- Chef::Log.info("#{new_resource} unmounted")
+ logger.info("#{new_resource} unmounted")
end
else
- Chef::Log.debug("#{new_resource} is already unmounted")
+ logger.trace("#{new_resource} is already unmounted")
end
end
- def action_remount
+ action :remount do
if current_resource.mounted
if new_resource.supports[:remount]
converge_by("remount #{current_resource.device}") do
remount_fs
- Chef::Log.info("#{new_resource} remounted")
+ logger.info("#{new_resource} remounted")
end
else
converge_by("unmount #{current_resource.device}") do
umount_fs
- Chef::Log.info("#{new_resource} unmounted")
+ logger.info("#{new_resource} unmounted")
end
wait_until_unmounted(unmount_retries)
converge_by("mount #{current_resource.device}") do
mount_fs
- Chef::Log.info("#{new_resource} mounted")
+ logger.info("#{new_resource} mounted")
end
end
else
- Chef::Log.debug("#{new_resource} not mounted, nothing to remount")
+ logger.trace("#{new_resource} not mounted, nothing to remount")
end
end
- def action_enable
- unless current_resource.enabled && mount_options_unchanged?
+ action :enable do
+ unless current_resource.enabled && mount_options_unchanged? && device_unchanged?
converge_by("enable #{current_resource.device}") do
enable_fs
- Chef::Log.info("#{new_resource} enabled")
+ logger.info("#{new_resource} enabled")
end
else
- Chef::Log.debug("#{new_resource} already enabled")
+ logger.trace("#{new_resource} already enabled")
end
end
- def action_disable
+ action :disable do
if current_resource.enabled
converge_by("disable #{current_resource.device}") do
disable_fs
- Chef::Log.info("#{new_resource} disabled")
+ logger.info("#{new_resource} disabled")
end
else
- Chef::Log.debug("#{new_resource} already disabled")
+ logger.trace("#{new_resource} already disabled")
end
end
+ alias :action_unmount :action_umount
+
#
# Abstract Methods to be implemented by subclasses
#
@@ -122,6 +118,17 @@ class Chef
raise Chef::Exceptions::UnsupportedAction, "#{self} does not implement #mount_options_unchanged?"
end
+ # It's entirely plausible that a site might prefer UUIDs or labels, so
+ # we need to be able to update fstab to conform with their wishes
+ # without necessarily needing to remount the device.
+ # See #6851 for more.
+ # We have to compare current resource device with device_fstab value
+ # because entry in /etc/fstab will be as per device_type.
+ # For Ex: 'LABEL=/tmp/ /mnt ext3 defaults 0 2', where 'device_type' is :label.
+ def device_unchanged?
+ @current_resource.device == device_fstab
+ end
+
#
# NOTE: for the following methods, this superclass will already have checked if the filesystem is
# enabled and/or mounted and they will be called in converge_by blocks, so most defensive checking
@@ -161,9 +168,24 @@ class Chef
if (tries -= 1) < 0
raise Chef::Exceptions::Mount, "Retries exceeded waiting for filesystem to unmount"
end
+
sleep 0.1
end
end
+
+ # Returns the new_resource device as per device_type
+ def device_fstab
+ # Removed "/" from the end of str, because it was causing idempotency issue.
+ device = @new_resource.device == "/" ? @new_resource.device : @new_resource.device.chomp("/")
+ case @new_resource.device_type
+ when :device
+ device
+ when :label
+ "LABEL=#{device}"
+ when :uuid
+ "UUID=#{device}"
+ end
+ end
end
end
end
diff --git a/lib/chef/provider/mount/aix.rb b/lib/chef/provider/mount/aix.rb
index 12f0d67e6b..2cb29f5858 100644
--- a/lib/chef/provider/mount/aix.rb
+++ b/lib/chef/provider/mount/aix.rb
@@ -1,6 +1,5 @@
#
-# Author::
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,20 +15,20 @@
# limitations under the License.
#
-require "chef/provider/mount"
+require_relative "../mount"
class Chef
class Provider
class Mount
class Aix < Chef::Provider::Mount::Mount
- provides :mount, platform: %w{aix}
+ provides :mount, platform: "aix"
# Override for aix specific handling
def initialize(new_resource, run_context)
super
# options and fstype are set to "defaults" and "auto" respectively in the Mount Resource class. These options are not valid for AIX, override them.
if @new_resource.options[0] == "defaults"
- @new_resource.options.clear
+ @new_resource.options([])
end
if @new_resource.fstype == "auto"
@new_resource.send(:clear_fstype)
@@ -40,30 +39,60 @@ class Chef
# Check to see if there is an entry in /etc/filesystems. Last entry for a volume wins. Using command "lsfs" to fetch entries.
enabled = false
+ regex_arr = device_fstab_regex.split(":")
+ if regex_arr.size == 2
+ nodename = regex_arr[0]
+ devicename = regex_arr[1]
+ else
+ devicename = regex_arr[0]
+ end
# lsfs o/p = #MountPoint:Device:Vfs:Nodename:Type:Size:Options:AutoMount:Acct
# search only for current mount point
- shell_out("lsfs -c #{@new_resource.mount_point}").stdout.each_line do |line|
+ shell_out("lsfs", "-c", @new_resource.mount_point).stdout.each_line do |line|
case line
when /^#\s/
next
- when /^#{Regexp.escape(@new_resource.mount_point)}:#{device_fstab_regex}:(\S+):(\[\S+\])?:(\S+)?:(\S+):(\S+):(\S+):(\S+)/
+ when /^#{Regexp.escape(@new_resource.mount_point)}:#{devicename}:(\S+):#{nodename}:(\S+)?:(\S+):(\S+):(\S+):(\S+)/
# mount point entry with ipv6 address for nodename (ipv6 address use ':')
enabled = true
@current_resource.fstype($1)
- @current_resource.options($5)
- Chef::Log.debug("Found mount #{device_fstab} to #{@new_resource.mount_point} in /etc/filesystems")
+ @current_resource.options($4)
+ logger.trace("Found mount point #{@new_resource.mount_point} :: device_type #{@current_resource.device_type} in /etc/filesystems")
next
- when /^#{Regexp.escape(@new_resource.mount_point)}:#{device_fstab_regex}::(\S+):(\S+)?:(\S+)?:(\S+):(\S+):(\S+):(\S+)/
+ when /^#{Regexp.escape(@new_resource.mount_point)}:#{nodename}:(\S+)::(\S+)?:(\S+):(\S+):(\S+):(\S+)/
# mount point entry with hostname or ipv4 address
enabled = true
@current_resource.fstype($1)
+ @current_resource.options($4)
+ @current_resource.device("")
+ logger.trace("Found mount point #{@new_resource.mount_point} :: device_type #{@current_resource.device_type} in /etc/filesystems")
+ next
+ when /^#{Regexp.escape(@new_resource.mount_point)}:(\S+)?:(\S+):#{devicename}:(\S+)?:(\S+):(\S+):(\S+):(\S+)/
+ # mount point entry with hostname or ipv4 address
+ enabled = true
+ @current_resource.fstype($2)
@current_resource.options($5)
- Chef::Log.debug("Found mount #{device_fstab} to #{@new_resource.mount_point} in /etc/filesystems")
+ @current_resource.device(devicename + "/")
+ logger.trace("Found mount point #{@new_resource.mount_point} :: device_type #{@current_resource.device_type} in /etc/filesystems")
next
- when /^#{Regexp.escape(@new_resource.mount_point)}/
- enabled = false
- Chef::Log.debug("Found conflicting mount point #{@new_resource.mount_point} in /etc/filesystems")
+ when /^#{Regexp.escape(@new_resource.mount_point)}:(.*?):(.*?):(.*?):(.*?):(.*?):(.*?):(.*?):(.*?)/
+ if $3.split("=")[0] == "LABEL" || $1.split("=")[0] == "LABEL"
+ @current_resource.device_type("label")
+ elsif $3.split("=")[0] == "UUID" || $1.split("=")[0] == "UUID"
+ @current_resource.device_type("uuid")
+ else
+ @current_resource.device_type("device")
+ end
+
+ if @current_resource.device_type != @new_resource.device_type
+ enabled = true
+ logger.trace("Found mount point #{@new_resource.mount_point} :: device_type #{@current_resource.device_type} in /etc/filesystems")
+ else
+ enabled = false
+ logger.trace("Found conflicting mount point #{@new_resource.mount_point} in /etc/filesystems")
+ end
end
+
end
@current_resource.enabled(enabled)
end
@@ -80,10 +109,10 @@ class Chef
case line
when /#{search_device}\s+#{Regexp.escape(@new_resource.mount_point)}/
mounted = true
- Chef::Log.debug("Special device #{device_logstring} mounted as #{@new_resource.mount_point}")
- when /^[\/\w]+\s+#{Regexp.escape(@new_resource.mount_point)}\s+/
+ logger.trace("Special device #{device_logstring} mounted as #{@new_resource.mount_point}")
+ when %r{^[/\w]+\s+#{Regexp.escape(@new_resource.mount_point)}\s+}
mounted = false
- Chef::Log.debug("Found conflicting mount point #{@new_resource.mount_point} in /etc/fstab")
+ logger.trace("Found conflicting mount point #{@new_resource.mount_point} in /etc/fstab")
end
end
@current_resource.mounted(mounted)
@@ -92,39 +121,40 @@ class Chef
def mount_fs
unless @current_resource.mounted
mountable?
- command = "mount -v #{@new_resource.fstype}"
+ command = [ "mount", "-v", @new_resource.fstype ]
- if !(@new_resource.options.nil? || @new_resource.options.empty?)
- command << " -o #{@new_resource.options.join(',')}"
+ unless @new_resource.options.nil? || @new_resource.options.empty?
+ command << "-o"
+ command << @new_resource.options.join(",")
end
command << case @new_resource.device_type
when :device
- " #{device_real}"
+ device_real
when :label
- " -L #{@new_resource.device}"
+ [ "-L", @new_resource.device ]
when :uuid
- " -U #{@new_resource.device}"
+ [ "-U", @new_resource.device ]
end
- command << " #{@new_resource.mount_point}"
+ command << @new_resource.mount_point
shell_out!(command)
- Chef::Log.debug("#{@new_resource} is mounted at #{@new_resource.mount_point}")
+ logger.trace("#{@new_resource} is mounted at #{@new_resource.mount_point}")
else
- Chef::Log.debug("#{@new_resource} is already mounted at #{@new_resource.mount_point}")
+ logger.trace("#{@new_resource} is already mounted at #{@new_resource.mount_point}")
end
end
def remount_command
if !(@new_resource.options.nil? || @new_resource.options.empty?)
- return "mount -o remount,#{@new_resource.options.join(',')} #{@new_resource.device} #{@new_resource.mount_point}"
+ [ "mount", "-o", "remount,#{@new_resource.options.join(",")}", @new_resource.device, @new_resource.mount_point ]
else
- return "mount -o remount #{@new_resource.device} #{@new_resource.mount_point}"
+ [ "mount", "-o", "remount", @new_resource.device, @new_resource.mount_point ]
end
end
def enable_fs
if @current_resource.enabled && mount_options_unchanged?
- Chef::Log.debug("#{@new_resource} is already enabled - nothing to do")
+ logger.trace("#{@new_resource} is already enabled - nothing to do")
return nil
end
@@ -134,7 +164,7 @@ class Chef
disable_fs
end
::File.open("/etc/filesystems", "a") do |fstab|
- fstab.puts("#{@new_resource.mount_point}:")
+ fstab.puts("\n\n#{@new_resource.mount_point}:")
if network_device?
device_details = device_fstab.split(":")
fstab.puts("\tdev\t\t= #{device_details[1]}")
@@ -144,25 +174,36 @@ class Chef
end
fstab.puts("\tvfs\t\t= #{@new_resource.fstype}")
fstab.puts("\tmount\t\t= false")
- fstab.puts "\toptions\t\t= #{@new_resource.options.join(',')}" unless @new_resource.options.nil? || @new_resource.options.empty?
- Chef::Log.debug("#{@new_resource} is enabled at #{@new_resource.mount_point}")
+ fstab.puts "\toptions\t\t= #{@new_resource.options.join(",")}" unless @new_resource.options.nil? || @new_resource.options.empty?
+ logger.trace("#{@new_resource} is enabled at #{@new_resource.mount_point}")
end
end
+ def mount_options_unchanged?
+ current_resource_options = @current_resource.options.delete_if { |x| x == "rw" }
+
+ @current_resource.device == @new_resource.device &&
+ @current_resource.fsck_device == @new_resource.fsck_device &&
+ @current_resource.fstype == @new_resource.fstype &&
+ current_resource_options == @new_resource.options &&
+ @current_resource.dump == @new_resource.dump &&
+ @current_resource.pass == @new_resource.pass
+ end
+
def disable_fs
contents = []
if @current_resource.enabled
found_device = false
::File.open("/etc/filesystems", "r").each_line do |line|
case line
- when /^\/.+:\s*$/
- if line =~ /#{Regexp.escape(@new_resource.mount_point)}+:/
+ when %r{^/.+:\s*$}
+ if /#{Regexp.escape(@new_resource.mount_point)}+:/.match?(line)
found_device = true
else
found_device = false
end
end
- if !found_device
+ unless found_device
contents << line
end
end
@@ -170,7 +211,7 @@ class Chef
contents.each { |line| fstab.puts line }
end
else
- Chef::Log.debug("#{@new_resource} is not enabled - nothing to do")
+ logger.trace("#{@new_resource} is not enabled - nothing to do")
end
end
diff --git a/lib/chef/provider/mount/linux.rb b/lib/chef/provider/mount/linux.rb
new file mode 100644
index 0000000000..382e37d41a
--- /dev/null
+++ b/lib/chef/provider/mount/linux.rb
@@ -0,0 +1,67 @@
+#
+# Author:: Antima Gupta (<agupta@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../mount"
+
+class Chef
+ class Provider
+ class Mount
+ class Linux < Chef::Provider::Mount::Mount
+
+ provides :mount, os: "linux"
+
+ # Check to see if the volume is mounted.
+ # "findmnt" outputs the mount points with volume.
+ # Convert the mount_point of the resource to a real path in case it
+ # contains symlinks in its parents dirs.
+
+ def mounted?
+ mounted = false
+
+ real_mount_point = if ::File.exists? @new_resource.mount_point
+ ::File.realpath(@new_resource.mount_point)
+ else
+ @new_resource.mount_point
+ end
+
+ shell_out!("findmnt -rn").stdout.each_line do |line|
+ case line
+ # Permalink for device already mounted to mount point for : https://rubular.com/r/L0RNnD4gf2DJGl
+ when /\A#{Regexp.escape(real_mount_point)}\s+#{device_mount_regex}\s/
+ mounted = true
+ logger.trace("Special device #{device_logstring} mounted as #{real_mount_point}")
+ # Permalink for multiple devices mounted to the same mount point(i.e. '/proc') https://rubular.com/r/a356yzspU7N9TY
+ when %r{\A#{Regexp.escape(real_mount_point)}\s+([/\w])+\s}
+ mounted = false
+ logger.trace("Special device #{$~[1]} mounted as #{real_mount_point}")
+ # Permalink for bind device mounted to an existing mount point: https://rubular.com/r/QAE0ilL3sm3Ldz
+ when %r{\A#{Regexp.escape(real_mount_point)}\s+([/\w])+\[#{device_mount_regex}\]\s}
+ mounted = true
+ logger.trace("Bind device #{device_logstring} mounted as #{real_mount_point}")
+ # Permalink for network device mounted to an existing mount point: https://rubular.com/r/JRTXXGFdQtwCD6
+ when /\A#{Regexp.escape(real_mount_point)}\s+#{device_mount_regex}\[/
+ mounted = true
+ logger.trace("Network device #{device_logstring} mounted as #{real_mount_point}")
+ end
+ end
+ @current_resource.mounted(mounted)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/mount/mount.rb b/lib/chef/provider/mount/mount.rb
index 07da6ac361..0bd81d5453 100644
--- a/lib/chef/provider/mount/mount.rb
+++ b/lib/chef/provider/mount/mount.rb
@@ -1,6 +1,6 @@
#
# Author:: Joshua Timberman (<joshua@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/provider/mount"
-require "chef/log"
+require_relative "../mount"
+require_relative "../../log"
class Chef
class Provider
@@ -47,27 +47,29 @@ class Chef
elsif @new_resource.mount_point != "none" && !::File.exists?(@new_resource.mount_point)
raise Chef::Exceptions::Mount, "Mount point #{@new_resource.mount_point} does not exist"
end
- return true
+
+ true
end
def enabled?
# Check to see if there is a entry in /etc/fstab. Last entry for a volume wins.
enabled = false
+ unless ::File.exist?("/etc/fstab")
+ logger.debug "/etc/fstab not found, treating mount as not-enabled"
+ return
+ end
::File.foreach("/etc/fstab") do |line|
case line
when /^[#\s]/
next
- when /^#{device_fstab_regex}\s+#{Regexp.escape(@new_resource.mount_point)}\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/
+ when /^(#{device_fstab_regex})\s+#{Regexp.escape(@new_resource.mount_point)}\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/
enabled = true
- @current_resource.fstype($1)
- @current_resource.options($2)
- @current_resource.dump($3.to_i)
- @current_resource.pass($4.to_i)
- Chef::Log.debug("Found mount #{device_fstab} to #{@new_resource.mount_point} in /etc/fstab")
- next
- when /^[\/\w]+\s+#{Regexp.escape(@new_resource.mount_point)}\s+/
- enabled = false
- Chef::Log.debug("Found conflicting mount point #{@new_resource.mount_point} in /etc/fstab")
+ @current_resource.device($1)
+ @current_resource.fstype($2)
+ @current_resource.options($3)
+ @current_resource.dump($4.to_i)
+ @current_resource.pass($5.to_i)
+ logger.trace("Found mount #{device_fstab} to #{@new_resource.mount_point} in /etc/fstab")
end
end
@current_resource.enabled(enabled)
@@ -89,10 +91,10 @@ class Chef
case line
when /^#{device_mount_regex}\s+on\s+#{Regexp.escape(real_mount_point)}\s/
mounted = true
- Chef::Log.debug("Special device #{device_logstring} mounted as #{real_mount_point}")
- when /^([\/\w])+\son\s#{Regexp.escape(real_mount_point)}\s+/
+ logger.trace("Special device #{device_logstring} mounted as #{real_mount_point}")
+ when %r{^([/\w])+\son\s#{Regexp.escape(real_mount_point)}\s+}
mounted = false
- Chef::Log.debug("Special device #{$~[1]} mounted as #{real_mount_point}")
+ logger.trace("Special device #{$~[1]} mounted as #{real_mount_point}")
end
end
@current_resource.mounted(mounted)
@@ -101,132 +103,113 @@ class Chef
def mount_fs
unless @current_resource.mounted
mountable?
- command = "mount -t #{@new_resource.fstype}"
- command << " -o #{@new_resource.options.join(',')}" unless @new_resource.options.nil? || @new_resource.options.empty?
+ command = [ "mount", "-t", @new_resource.fstype ]
+ unless @new_resource.options.nil? || @new_resource.options.empty?
+ command << "-o"
+ command << @new_resource.options.join(",")
+ end
command << case @new_resource.device_type
when :device
- " #{device_real}"
+ device_real
when :label
- " -L #{@new_resource.device}"
+ [ "-L", @new_resource.device ]
when :uuid
- " -U #{@new_resource.device}"
+ [ "-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}")
+ command << @new_resource.mount_point
+ shell_out!(*command)
+ logger.trace("#{@new_resource} is mounted at #{@new_resource.mount_point}")
else
- Chef::Log.debug("#{@new_resource} is already mounted at #{@new_resource.mount_point}")
+ logger.trace("#{@new_resource} is already mounted at #{@new_resource.mount_point}")
end
end
def umount_fs
if @current_resource.mounted
- shell_out!("umount #{@new_resource.mount_point}")
- Chef::Log.debug("#{@new_resource} is no longer mounted at #{@new_resource.mount_point}")
+ shell_out!("umount", @new_resource.mount_point)
+ logger.trace("#{@new_resource} is no longer mounted at #{@new_resource.mount_point}")
else
- Chef::Log.debug("#{@new_resource} is not mounted at #{@new_resource.mount_point}")
+ logger.trace("#{@new_resource} is not mounted at #{@new_resource.mount_point}")
end
end
def remount_command
- return "mount -o remount,#{@new_resource.options.join(',')} #{@new_resource.mount_point}"
+ [ "mount", "-o", "remount,#{@new_resource.options.join(",")}", @new_resource.mount_point ]
end
def remount_fs
if @current_resource.mounted && @new_resource.supports[:remount]
- shell_out!(remount_command)
+ shell_out!(*remount_command)
@new_resource.updated_by_last_action(true)
- Chef::Log.debug("#{@new_resource} is remounted at #{@new_resource.mount_point}")
+ logger.trace("#{@new_resource} is remounted at #{@new_resource.mount_point}")
elsif @current_resource.mounted
umount_fs
sleep 1
mount_fs
else
- Chef::Log.debug("#{@new_resource} is not mounted at #{@new_resource.mount_point} - nothing to do")
+ logger.trace("#{@new_resource} is not mounted at #{@new_resource.mount_point} - nothing to do")
end
end
+ # Return appropriate default mount options according to the given os.
+ def default_mount_options
+ linux? ? "defaults" : "rw"
+ end
+
def enable_fs
- if @current_resource.enabled && mount_options_unchanged?
- Chef::Log.debug("#{@new_resource} is already enabled - nothing to do")
+ if @current_resource.enabled && mount_options_unchanged? && device_unchanged?
+ logger.trace("#{@new_resource} is already enabled - nothing to do")
return nil
end
if @current_resource.enabled
# The current options don't match what we have, so
- # disable, then enable.
- disable_fs
- end
- ::File.open("/etc/fstab", "a") do |fstab|
- fstab.puts("#{device_fstab} #{@new_resource.mount_point} #{@new_resource.fstype} #{@new_resource.options.nil? ? "defaults" : @new_resource.options.join(",")} #{@new_resource.dump} #{@new_resource.pass}")
- Chef::Log.debug("#{@new_resource} is enabled at #{@new_resource.mount_point}")
+ # update the last matching entry with current option
+ # and order will remain the same.
+ edit_fstab
+ else
+ ::File.open("/etc/fstab", "a") do |fstab|
+ fstab.puts("#{device_fstab} #{@new_resource.mount_point} #{@new_resource.fstype} #{@new_resource.options.nil? ? default_mount_options : @new_resource.options.join(",")} #{@new_resource.dump} #{@new_resource.pass}")
+ logger.trace("#{@new_resource} is enabled at #{@new_resource.mount_point}")
+ end
end
end
def disable_fs
- if @current_resource.enabled
- contents = []
-
- found = false
- ::File.readlines("/etc/fstab").reverse_each do |line|
- if !found && line =~ /^#{device_fstab_regex}\s+#{Regexp.escape(@new_resource.mount_point)}\s/
- found = true
- Chef::Log.debug("#{@new_resource} is removed from fstab")
- next
- else
- contents << line
- end
- end
-
- ::File.open("/etc/fstab", "w") do |fstab|
- contents.reverse_each { |line| fstab.puts line }
- end
- else
- Chef::Log.debug("#{@new_resource} is not enabled - nothing to do")
- end
+ edit_fstab(remove: true)
end
def network_device?
- @new_resource.device =~ /:/ || @new_resource.device =~ /\/\//
+ @new_resource.device.include?(":") || @new_resource.device.include?("//")
end
def device_should_exist?
( @new_resource.device != "none" ) &&
( not network_device? ) &&
- ( not %w{ cgroup tmpfs fuse vboxsf }.include? @new_resource.fstype )
+ ( not %w{ cgroup tmpfs fuse vboxsf zfs }.include? @new_resource.fstype )
end
private
- def device_fstab
- case @new_resource.device_type
- when :device
- @new_resource.device
- when :label
- "LABEL=#{@new_resource.device}"
- when :uuid
- "UUID=#{@new_resource.device}"
- end
- end
-
def device_real
- if @real_device == nil
+ if @real_device.nil?
if @new_resource.device_type == :device
@real_device = @new_resource.device
else
@real_device = ""
- ret = shell_out("/sbin/findfs #{device_fstab}")
+ ret = shell_out("/sbin/findfs", device_fstab)
device_line = ret.stdout.lines.first # stdout.first consumes
@real_device = device_line.chomp unless device_line.nil?
end
end
- @real_device
+ # Removed "/" from the end of str, because it was causing idempotency issue.
+ @real_device == "/" ? @real_device : @real_device.chomp("/")
end
def device_logstring
case @new_resource.device_type
when :device
- "#{device_real}"
+ (device_real).to_s
when :label
"#{device_real} with label #{@new_resource.device}"
when :uuid
@@ -253,7 +236,7 @@ class Chef
if @new_resource.device_type == :device
device_mount_regex
else
- device_fstab
+ Regexp.union(device_fstab, device_mount_regex)
end
end
@@ -264,6 +247,35 @@ class Chef
@current_resource.pass == @new_resource.pass
end
+ # It will update or delete the entry from fstab.
+ def edit_fstab(remove: false)
+ if @current_resource.enabled
+ contents = []
+
+ found = false
+ ::File.readlines("/etc/fstab").reverse_each do |line|
+ if !found && line =~ /^#{device_fstab_regex}\s+#{Regexp.escape(@new_resource.mount_point)}\s/
+ found = true
+ if remove
+ logger.trace("#{@new_resource} is removed from fstab")
+ else
+ contents << ("#{device_fstab} #{@new_resource.mount_point} #{@new_resource.fstype} #{@new_resource.options.nil? ? default_mount_options : @new_resource.options.join(",")} #{@new_resource.dump} #{@new_resource.pass}")
+ logger.trace("#{@new_resource} is updated with new content in fstab")
+ end
+ next
+ else
+ contents << line
+ end
+ end
+
+ ::File.open("/etc/fstab", "w") do |fstab|
+ contents.reverse_each { |line| fstab.puts line }
+ end
+ else
+ logger.trace("#{@new_resource} is not enabled - nothing to do")
+ end
+ end
+
end
end
end
diff --git a/lib/chef/provider/mount/solaris.rb b/lib/chef/provider/mount/solaris.rb
index a5a7a327cb..245e04f40e 100644
--- a/lib/chef/provider/mount/solaris.rb
+++ b/lib/chef/provider/mount/solaris.rb
@@ -1,8 +1,7 @@
-# Encoding: utf-8
# Author:: Hugo Fichter
# Author:: Lamont Granquist (<lamont@chef.io>)
# Author:: Joshua Timberman (<joshua@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software, Inc
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,16 +17,16 @@
# limitations under the License.
#
-require "chef/provider/mount"
-require "chef/log"
-require "forwardable"
+require_relative "../mount"
+require_relative "../../log"
+require "forwardable" unless defined?(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}
+ provides :mount, platform_family: "solaris_based"
extend Forwardable
@@ -74,24 +73,27 @@ class Chef
end
def mount_fs
- actual_options = options || []
- actual_options.delete("noauto")
- command = "mount -F #{fstype}"
- command << " -o #{actual_options.join(',')}" unless actual_options.empty?
- command << " #{device} #{mount_point}"
+ actual_options = native_options(options)
+ actual_options.delete("-")
+ command = [ "mount", "-F", fstype ]
+ unless actual_options.empty?
+ command << "-o"
+ command << actual_options.join(",")
+ end
+ command << [ device, mount_point ]
shell_out!(command)
end
def umount_fs
- shell_out!("umount #{mount_point}")
+ shell_out!("umount", mount_point)
end
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(',')}"
- shell_out!("mount -o remount#{mount_options} #{mount_point}")
+ actual_options = native_options(options)
+ actual_options.delete("-")
+ mount_options = actual_options.empty? ? "" : ",#{actual_options.join(",")}"
+ shell_out!("mount", "-o", "remount#{mount_options}", mount_point)
end
def enable_fs
@@ -112,7 +114,7 @@ class Chef
else
# this is likely some kind of internal error, since we should only call disable_fs when there
# the filesystem we want to disable is enabled.
- Chef::Log.warn("#{new_resource} did not find the mountpoint to disable in the vfstab")
+ logger.warn("#{new_resource} did not find the mountpoint to disable in the vfstab")
end
end
@@ -121,8 +123,8 @@ class Chef
end
def mount_options_unchanged?
- new_options = options_remove_noauto(options)
- current_options = options_remove_noauto(current_resource.nil? ? nil : current_resource.options)
+ new_options = native_options(options)
+ current_options = native_options(current_resource.nil? ? nil : current_resource.options)
current_resource.fsck_device == fsck_device &&
current_resource.fstype == fstype &&
@@ -150,13 +152,13 @@ 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}")
+ logger.trace("Special device #{device} is mounted as #{mount_point}")
mounted = true
- when /^([\/\w]+)\son\s#{Regexp.escape(mount_point)}\s+/
- Chef::Log.debug("Special device #{Regexp.last_match[1]} is mounted as #{mount_point}")
+ when %r{^([/\w]+)\son\s#{Regexp.escape(mount_point)}\s+}
+ logger.trace("Special device #{Regexp.last_match[1]} is mounted as #{mount_point}")
mounted = false
end
end
@@ -168,7 +170,8 @@ class Chef
def read_vfstab_status
# Check to see if there is an entry in /etc/vfstab. Last entry for a volume wins.
enabled = false
- fstype = options = pass = nil
+ pass = false
+ fstype = options = nil
::File.foreach(VFSTAB) do |line|
case line
when /^[#\s]/
@@ -176,7 +179,7 @@ class Chef
# solaris /etc/vfstab format:
# device device mount FS fsck mount mount
# to mount to fsck point type pass at boot options
- when /^#{device_regex}\s+[-\/\w]+\s+#{Regexp.escape(mount_point)}\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/
+ when %r{^#{device_regex}\s+[-/\w]+\s+#{Regexp.escape(mount_point)}\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)}
enabled = true
fstype = Regexp.last_match[1]
options = Regexp.last_match[4]
@@ -190,12 +193,12 @@ class Chef
end
end
pass = (Regexp.last_match[2] == "-") ? 0 : Regexp.last_match[2].to_i
- Chef::Log.debug("Found mount #{device} to #{mount_point} in #{VFSTAB}")
+ logger.trace("Found mount #{device} to #{mount_point} in #{VFSTAB}")
next
- when /^[-\/\w]+\s+[-\/\w]+\s+#{Regexp.escape(mount_point)}\s+/
+ when %r{^[-/\w]+\s+[-/\w]+\s+#{Regexp.escape(mount_point)}\s+}
# if we find a mountpoint on top of our mountpoint, then we are not enabled
enabled = false
- Chef::Log.debug("Found conflicting mount point #{mount_point} in #{VFSTAB}")
+ logger.trace("Found conflicting mount point #{mount_point} in #{VFSTAB}")
end
end
[enabled, fstype, options, pass]
@@ -220,11 +223,7 @@ class Chef
end
def vfstab_entry
- actual_options = unless options.nil?
- tempops = options.dup
- tempops.delete("noauto")
- tempops
- end
+ actual_options = native_options(options)
autostr = mount_at_boot? ? "yes" : "no"
passstr = pass == 0 ? "-" : pass
optstr = (actual_options.nil? || actual_options.empty?) ? "-" : actual_options.join(",")
@@ -237,7 +236,7 @@ class Chef
::File.readlines(VFSTAB).reverse_each do |line|
if !found && line =~ /^#{device_regex}\s+\S+\s+#{Regexp.escape(mount_point)}/
found = true
- Chef::Log.debug("#{new_resource} is removed from vfstab")
+ logger.trace("#{new_resource} is removed from vfstab")
next
end
contents << line
@@ -251,11 +250,15 @@ class Chef
contents << vfstab_entry
end
- def options_remove_noauto(temp_options)
- new_options = []
- new_options += temp_options.nil? ? [] : temp_options
- new_options.delete("noauto")
- new_options
+ def native_options(temp_options)
+ if temp_options == %w{defaults}
+ ["-"]
+ else
+ new_options = []
+ new_options += temp_options.nil? ? [] : temp_options.dup
+ new_options.delete("noauto")
+ new_options
+ end
end
def device_regex
diff --git a/lib/chef/provider/mount/windows.rb b/lib/chef/provider/mount/windows.rb
index 0fb5aa7645..81e9ad6b1d 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"
-if RUBY_PLATFORM =~ /mswin|mingw32|windows/
- require "chef/util/windows/net_use"
- require "chef/util/windows/volume"
+require_relative "../mount"
+if RUBY_PLATFORM.match?(/mswin|mingw32|windows/)
+ require_relative "../../util/windows/net_use"
+ require_relative "../../util/windows/volume"
end
class Chef
@@ -30,7 +30,7 @@ class Chef
provides :mount, os: "windows"
def is_volume(name)
- name =~ /^\\\\\?\\Volume\{[\w-]+\}\\$/ ? true : false
+ /^\\\\\?\\Volume\{[\w-]+\}\\$/.match?(name) ? true : false
end
def initialize(new_resource, run_context)
@@ -40,43 +40,43 @@ class Chef
def load_current_resource
if is_volume(@new_resource.device)
- @mount = Chef::Util::Windows::Volume.new(@new_resource.name)
- else #assume network drive
- @mount = Chef::Util::Windows::NetUse.new(@new_resource.name)
+ @mount = Chef::Util::Windows::Volume.new(@new_resource.mount_point)
+ else # assume network drive
+ @mount = Chef::Util::Windows::NetUse.new(@new_resource.mount_point)
end
@current_resource = Chef::Resource::Mount.new(@new_resource.name)
@current_resource.mount_point(@new_resource.mount_point)
- Chef::Log.debug("Checking for mount point #{@current_resource.mount_point}")
+ logger.trace("Checking for mount point #{@current_resource.mount_point}")
begin
@current_resource.device(@mount.device)
- Chef::Log.debug("#{@current_resource.device} mounted on #{@new_resource.mount_point}")
+ logger.trace("#{@current_resource.device} mounted on #{@new_resource.mount_point}")
@current_resource.mounted(true)
rescue ArgumentError => e
@current_resource.mounted(false)
- Chef::Log.debug("#{@new_resource.mount_point} is not mounted: #{e.message}")
+ logger.trace("#{@new_resource.mount_point} is not mounted: #{e.message}")
end
end
def mount_fs
unless @current_resource.mounted
- @mount.add(:remote => @new_resource.device,
- :username => @new_resource.username,
- :domainname => @new_resource.domain,
- :password => @new_resource.password)
- Chef::Log.debug("#{@new_resource} is mounted at #{@new_resource.mount_point}")
+ @mount.add(remote: @new_resource.device,
+ username: @new_resource.username,
+ domainname: @new_resource.domain,
+ password: @new_resource.password)
+ logger.trace("#{@new_resource} is mounted at #{@new_resource.mount_point}")
else
- Chef::Log.debug("#{@new_resource} is already mounted at #{@new_resource.mount_point}")
+ logger.trace("#{@new_resource} is already mounted at #{@new_resource.mount_point}")
end
end
def umount_fs
if @current_resource.mounted
@mount.delete
- Chef::Log.debug("#{@new_resource} is no longer mounted at #{@new_resource.mount_point}")
+ logger.trace("#{@new_resource} is no longer mounted at #{@new_resource.mount_point}")
else
- Chef::Log.debug("#{@new_resource} is not mounted at #{@new_resource.mount_point}")
+ logger.trace("#{@new_resource} is not mounted at #{@new_resource.mount_point}")
end
end
diff --git a/lib/chef/provider/noop.rb b/lib/chef/provider/noop.rb
index 207bf7dedb..74e24f6a44 100644
--- a/lib/chef/provider/noop.rb
+++ b/lib/chef/provider/noop.rb
@@ -1,6 +1,6 @@
#
# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright (c) 2016 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,8 +26,8 @@ class Chef
end
def method_missing(method_sym, *arguments, &block)
- if method_sym.to_s =~ /^action_/
- Chef::Log.debug("NoOp-ing for #{method_sym}")
+ if /^action_/.match?(method_sym.to_s)
+ logger.trace("NoOp-ing for #{method_sym}")
else
super
end
diff --git a/lib/chef/provider/ohai.rb b/lib/chef/provider/ohai.rb
deleted file mode 100644
index 6b5a605ed5..0000000000
--- a/lib/chef/provider/ohai.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-#
-# Author:: Michael Leianrtas (<mleinartas@gmail.com>)
-# Copyright:: Copyright 2010-2016, Michael Leinartas
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require "ohai"
-
-class Chef
- class Provider
- class Ohai < Chef::Provider
- provides :ohai
-
- def whyrun_supported?
- true
- end
-
- def load_current_resource
- true
- end
-
- def action_reload
- converge_by("re-run ohai and merge results into node attributes") do
- ohai = ::Ohai::System.new
-
- # If @new_resource.plugin is nil, ohai will reload all the plugins
- # Otherwise it will only reload the specified plugin
- # Note that any changes to plugins, or new plugins placed on
- # the path are picked up by ohai.
- ohai.all_plugins @new_resource.plugin
- node.automatic_attrs.merge! ohai.data
- Chef::Log.info("#{@new_resource} reloaded")
- end
- end
- end
- end
-end
diff --git a/lib/chef/provider/osx_profile.rb b/lib/chef/provider/osx_profile.rb
deleted file mode 100644
index 69ecf2ddb9..0000000000
--- a/lib/chef/provider/osx_profile.rb
+++ /dev/null
@@ -1,255 +0,0 @@
-#
-# Author:: Nate Walck (<nate.walck@gmail.com>)
-# Copyright:: Copyright 2015-2016, 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 = nil
- if all_profiles && !all_profiles.empty?
- current_profile = all_profiles["_computerlevel"].find do |item|
- item["ProfileIdentifier"] == @new_profile_identifier
- end
- end
- @current_resource.profile(current_profile)
- end
-
- def define_resource_requirements
- requirements.assert(:remove) do |a|
- if @new_profile_identifier
- a.assertion do
- !@new_profile_identifier.nil? &&
- !@new_profile_identifier.end_with?(".mobileconfig") &&
- /^\w+(?:(\.| )\w+)+$/.match(@new_profile_identifier)
- end
- 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 do
- !new_profile_name.end_with?(".mobileconfig") &&
- /^\w+(?:(\.| )\w+)+$/.match(new_profile_name)
- end
- 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 do
- @new_profile_hash.include?("PayloadIdentifier")
- end
- a.failure_message RuntimeError, "The specified profile does not seem to be valid"
- end
- if @new_profile_hash.is_a?(String)
- a.assertion do
- @new_profile_hash.end_with?(".mobileconfig")
- end
- 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? || @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 3f641145e6..198286c75f 100644
--- a/lib/chef/provider/package.rb
+++ b/lib/chef/provider/package.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,26 +16,53 @@
# limitations under the License.
#
-require "chef/mixin/shell_out"
-require "chef/mixin/command"
-require "chef/mixin/subclass_directive"
-require "chef/log"
-require "chef/file_cache"
-require "chef/platform"
-require "chef/decorator/lazy_array"
+require_relative "../mixin/subclass_directive"
+require_relative "../log"
+require_relative "../file_cache"
+require_relative "../platform"
+require_relative "../decorator/lazy_array"
+require "shellwords" unless defined?(Shellwords)
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
+ # subclasses declare this if they want all their arguments as arrays of packages and names.
+ # any new packages using this should also use allow_nils below.
+ #
subclass_directive :use_multipackage_api
- # subclasses declare this if they want sources (filenames) pulled from their package names
+
+ # subclasses declare this if they want sources (filenames) pulled from their package names.
+ # this is for package providers that take a path into the filesystem (rpm, dpkg).
+ #
subclass_directive :use_package_name_for_source
+ # keeps package_names_for_targets and versions_for_targets indexed the same as package_name at
+ # the cost of having the subclass needing to deal with nils. all providers are encouraged to
+ # migrate to using this as it simplifies dealing with package aliases in subclasses.
+ #
+ subclass_directive :allow_nils
+
+ # subclasses that implement complex pattern matching using constraints, particularly the yum and
+ # dnf classes, should filter the installed version against the desired version constraint and
+ # return nil if it does not match. this means that 'nil' does not mean that no version of the
+ # package is installed, but that the installed version does not satisfy the desired constraints.
+ # (the package plus the constraints are not installed)
+ #
+ # [ this may arguably be useful for all package providers and it greatly simplifies the logic
+ # in the superclass that gets executed, so maybe this should always be used now? ]
+ #
+ # note that when using this feature that the current_resource.version must be loaded with the
+ # correct currently installed version, without doing the filtering -- for reporting and for
+ # correctly displaying version upgrades. that means there are 3 different arrays which must be
+ # loaded by the subclass: candidate_version, magic_version and current_resource.version.
+ #
+ # NOTE: magic_version is a terrible name, but I couldn't think of anything better, at least this
+ # way it stands out clearly.
+ #
+ subclass_directive :use_magic_version
+
#
# Hook that subclasses use to populate the candidate_version(s)
#
@@ -47,20 +74,19 @@ class Chef
@candidate_version = nil
end
- def whyrun_supported?
- true
+ def options
+ new_resource.options
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
+ 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
+ def load_current_resource; end
def define_resource_requirements
# XXX: upgrade with a specific version doesn't make a whole lot of sense, but why don't we throw this anyway if it happens?
@@ -81,26 +107,19 @@ class Chef
end
end
- def action_install
+ action :install do
unless target_version_array.any?
- Chef::Log.debug("#{@new_resource} is already installed - nothing to do")
+ logger.trace("#{new_resource} is already installed - nothing to do")
return
end
- # @todo: move the preseed code out of the base class (and complete the fix for Array of preseeds? ugh...)
- if @new_resource.response_file
- if preseed_file = get_preseed_file(package_names_for_targets, versions_for_targets)
- converge_by("preseed package #{package_names_for_targets}") do
- preseed_package(preseed_file)
- end
- end
- end
+ prepare_for_installation
converge_by(install_description) do
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}")
+ logger.info("#{new_resource} installed #{package_names_for_targets} at #{versions_for_targets}")
end
end
@@ -108,6 +127,7 @@ class Chef
description = []
target_version_array.each_with_index do |target_version, i|
next if target_version.nil?
+
package_name = package_name_array[i]
description << "install version #{target_version} of package #{package_name}"
end
@@ -116,9 +136,9 @@ class Chef
private :install_description
- def action_upgrade
- if !target_version_array.any?
- Chef::Log.debug("#{@new_resource} no versions to upgrade - nothing to do")
+ action :upgrade do
+ unless target_version_array.any?
+ logger.trace("#{new_resource} no versions to upgrade - nothing to do")
return
end
@@ -127,7 +147,7 @@ class Chef
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}")
+ logger.info("#{new_resource} upgraded#{log_allow_downgrade} #{package_names_for_targets} to #{versions_for_targets}")
end
end
@@ -136,6 +156,7 @@ class Chef
description = []
target_version_array.each_with_index do |target_version, i|
next if target_version.nil?
+
package_name = package_name_array[i]
candidate_version = candidate_version_array[i]
current_version = current_version_array[i] || "uninstalled"
@@ -146,17 +167,17 @@ class Chef
private :upgrade_description
- def action_remove
+ action :remove do
if removing_package?
- description = @new_resource.version ? "version #{@new_resource.version} of " : ""
- converge_by("remove #{description}package #{@current_resource.package_name}") do
- multipackage_api_adapter(@current_resource.package_name, @new_resource.version) do |name, version|
+ description = new_resource.version ? "version #{new_resource.version} of " : ""
+ 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")
+ logger.info("#{new_resource} removed")
end
else
- Chef::Log.debug("#{@new_resource} package does not exist - nothing to do")
+ logger.trace("#{new_resource} package does not exist - nothing to do")
end
end
@@ -181,43 +202,64 @@ class Chef
end
end
- def action_purge
+ action :purge do
if removing_package?
- description = @new_resource.version ? "version #{@new_resource.version} of" : ""
- converge_by("purge #{description} package #{@current_resource.package_name}") do
- multipackage_api_adapter(@current_resource.package_name, @new_resource.version) do |name, version|
+ description = new_resource.version ? "version #{new_resource.version} of" : ""
+ converge_by("purge #{description} package #{current_resource.package_name}") do
+ multipackage_api_adapter(current_resource.package_name, new_resource.version) do |name, version|
purge_package(name, version)
end
- Chef::Log.info("#{@new_resource} purged")
+ logger.info("#{new_resource} purged")
end
end
end
- def action_reconfig
- if @current_resource.version == nil
- Chef::Log.debug("#{@new_resource} is NOT installed - nothing to do")
- return
- end
-
- unless @new_resource.response_file
- Chef::Log.debug("#{@new_resource} no response_file provided - nothing to do")
- return
+ action :lock do
+ packages_locked = if respond_to?(:packages_all_locked?, true)
+ packages_all_locked?(Array(new_resource.package_name), Array(new_resource.version))
+ else
+ package_locked(new_resource.package_name, new_resource.version)
+ end
+ unless packages_locked
+ description = new_resource.version ? "version #{new_resource.version} of " : ""
+ converge_by("lock #{description}package #{current_resource.package_name}") do
+ multipackage_api_adapter(current_resource.package_name, new_resource.version) do |name, version|
+ lock_package(name, version)
+ logger.info("#{new_resource} locked")
+ end
+ end
+ else
+ logger.trace("#{new_resource} is already locked")
end
+ end
- 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)
- multipackage_api_adapter(@new_resource.package_name, @current_resource.version) do |name, version|
- reconfig_package(name, version)
-
+ action :unlock do
+ packages_unlocked = if respond_to?(:packages_all_unlocked?, true)
+ packages_all_unlocked?(Array(new_resource.package_name), Array(new_resource.version))
+ else
+ !package_locked(new_resource.package_name, new_resource.version)
+ end
+ unless packages_unlocked
+ description = new_resource.version ? "version #{new_resource.version} of " : ""
+ converge_by("unlock #{description}package #{current_resource.package_name}") do
+ multipackage_api_adapter(current_resource.package_name, new_resource.version) do |name, version|
+ unlock_package(name, version)
+ logger.info("#{new_resource} unlocked")
end
- Chef::Log.info("#{@new_resource} reconfigured")
end
else
- Chef::Log.debug("#{@new_resource} preseeding has not changed - nothing to do")
+ logger.trace("#{new_resource} is already unlocked")
end
end
+ # for multipackage just implement packages_all_[un]locked? properly and omit implementing this API
+ def package_locked(name, version)
+ raise Chef::Exceptions::UnsupportedAction, "#{self} has no way to detect if package is locked"
+ end
+
+ # Subclasses will override this to a method and provide a preseed file path
+ def prepare_for_installation; end
+
# @todo use composition rather than inheritance
def multipackage_api_adapter(name, version)
@@ -248,13 +290,27 @@ class Chef
raise Chef::Exceptions::UnsupportedAction, "#{self} does not support pre-seeding package install/upgrade instructions"
end
- def reconfig_package(name, version)
+ def reconfig_package(name)
raise( Chef::Exceptions::UnsupportedAction, "#{self} does not support :reconfig" )
end
+ def lock_package(name, version)
+ raise( Chef::Exceptions::UnsupportedAction, "#{self} does not support :lock" )
+ end
+
+ def unlock_package(name, version)
+ raise( Chef::Exceptions::UnsupportedAction, "#{self} does not support :unlock" )
+ end
+
# used by subclasses. deprecated. use #a_to_s instead.
def expand_options(options)
- options ? " #{options}" : ""
+ # its deprecated but still work to do to deprecate it fully
+ # Chef.deprecated(:package_misc, "expand_options is deprecated, use shell_out instead")
+ if options
+ " #{options.is_a?(Array) ? Shellwords.join(options) : options}"
+ else
+ ""
+ end
end
# Check the current_version against either the candidate_version or the new_version
@@ -264,21 +320,56 @@ class Chef
#
# This MUST have 'equality' semantics -- the exact thing matches the exact thing.
#
- # The current_version should probably be dropped out of the method signature, it should
- # always be the first argument.
- #
# The name is not just bad, but i find it completely misleading, consider:
#
# target_version_already_installed?(current_version, new_version)
# target_version_already_installed?(current_version, candidate_version)
#
- # which of those is the 'target_version'? i'd say the new_version and i'm confused when
+ # Which of those is the 'target_version'? I'd say the new_version and I'm confused when
# i see it called with the candidate_version.
#
- # `current_version_equals?(version)` would be a better name
+ # `version_equals?(v1, v2)` would be a better name.
+ #
+ # Note that most likely we need a spaceship operator on versions that subclasses can implement
+ # and we should have `version_compare(v1, v2)` that returns `v1 <=> v2`.
+
+ # This method performs a strict equality check between two strings representing version numbers
+ #
+ # This function will eventually be deprecated in favour of the below version_equals function.
+
def target_version_already_installed?(current_version, target_version)
- return false unless current_version && target_version
- current_version == target_version
+ version_equals?(current_version, target_version)
+ end
+
+ # Note that most likely we need a spaceship operator on versions that subclasses can implement
+ # and we should have `version_compare(v1, v2)` that returns `v1 <=> v2`.
+
+ # This method performs a strict equality check between two strings representing version numbers
+ #
+ def version_equals?(v1, v2)
+ return false unless v1 && v2
+
+ v1 == v2
+ end
+
+ # This function compares two version numbers and returns 'spaceship operator' style results, ie:
+ # if v1 < v2 then return -1
+ # if v1 = v2 then return 0
+ # if v1 > v2 then return 1
+ # if v1 and v2 are not comparable then return nil
+ #
+ # By default, this function will use Gem::Version comparison. Subclasses can reimplement this method
+ # for package-management system specific versions.
+ #
+ # (In other words, pull requests to introduce domain specific mangling of versions into this method
+ # will be closed -- that logic must go into the subclass -- we understand that this is far from perfect
+ # but it is a better default than outright buggy things like v1.to_f <=> v2.to_f)
+ #
+ def version_compare(v1, v2)
+ gem_v1 = Gem::Version.new(v1.gsub(/\A\s*(#{Gem::Version::VERSION_PATTERN}).*/, '\1'))
+ gem_v2 = Gem::Version.new(v2.gsub(/\A\s*(#{Gem::Version::VERSION_PATTERN}).*/, '\1'))
+
+ gem_v1 <=> gem_v2
end
# Check the current_version against the new_resource.version, possibly using fuzzy
@@ -286,54 +377,12 @@ class Chef
#
# Subclasses MAY override this to provide fuzzy matching on the resource ('>=' and '~>' stuff)
#
- # This should only ever be offered the same arguments (so they should most likely be
- # removed from the method signature).
+ # `version_satisfied_by?(version, constraint)` might be a better name to make this generic.
#
- # `new_version_satisfied?()` might be a better name
def version_requirement_satisfied?(current_version, new_version)
target_version_already_installed?(current_version, new_version)
end
- # @todo: extract apt/dpkg specific preseeding to a helper class
- def get_preseed_file(name, version)
- resource = preseed_resource(name, version)
- resource.run_action(:create)
- Chef::Log.debug("#{@new_resource} fetched preseed file to #{resource.path}")
-
- if resource.updated_by_last_action?
- resource.path
- else
- false
- end
- end
-
- # @todo: extract apt/dpkg specific preseeding to a helper class
- def preseed_resource(name, version)
- # A directory in our cache to store this cookbook's preseed files in
- file_cache_dir = Chef::FileCache.create_cache_path("preseed/#{@new_resource.cookbook_name}")
- # The full path where the preseed file will be stored
- cache_seed_to = "#{file_cache_dir}/#{name}-#{version}.seed"
-
- Chef::Log.debug("#{@new_resource} fetching preseed file to #{cache_seed_to}")
-
- if template_available?(@new_resource.response_file)
- Chef::Log.debug("#{@new_resource} fetching preseed file via Template")
- remote_file = Chef::Resource::Template.new(cache_seed_to, run_context)
- remote_file.variables(@new_resource.response_file_variables)
- elsif cookbook_file_available?(@new_resource.response_file)
- Chef::Log.debug("#{@new_resource} fetching preseed file via cookbook_file")
- remote_file = Chef::Resource::CookbookFile.new(cache_seed_to, run_context)
- else
- message = "No template or cookbook file found for response file #{@new_resource.response_file}"
- raise Chef::Exceptions::FileNotFound, message
- end
-
- remote_file.cookbook_name = @new_resource.cookbook_name
- remote_file.source(@new_resource.response_file)
- remote_file.backup(false)
- remote_file
- end
-
# helper method used by subclasses
#
def as_array(thing)
@@ -352,9 +401,12 @@ class Chef
def package_names_for_targets
package_names_for_targets = []
target_version_array.each_with_index do |target_version, i|
- next if target_version.nil?
- package_name = package_name_array[i]
- package_names_for_targets.push(package_name)
+ if !target_version.nil?
+ package_name = package_name_array[i]
+ package_names_for_targets.push(package_name)
+ else
+ package_names_for_targets.push(nil) if allow_nils?
+ end
end
multipackage? ? package_names_for_targets : package_names_for_targets[0]
end
@@ -369,8 +421,11 @@ class Chef
def versions_for_targets
versions_for_targets = []
target_version_array.each_with_index do |target_version, i|
- next if target_version.nil?
- versions_for_targets.push(target_version)
+ if !target_version.nil?
+ versions_for_targets.push(target_version)
+ else
+ versions_for_targets.push(nil) if allow_nils?
+ end
end
multipackage? ? versions_for_targets : versions_for_targets[0]
end
@@ -383,40 +438,51 @@ class Chef
@target_version_array ||=
begin
target_version_array = []
-
each_package do |package_name, new_version, current_version, candidate_version|
case action
when :upgrade
- if target_version_already_installed?(current_version, new_version)
- # this is an odd use case
- Chef::Log.debug("#{new_resource} #{package_name} #{new_version} is already installed -- you are equality pinning with an :upgrade action, this may be deprecated in the future")
- target_version_array.push(nil)
- elsif target_version_already_installed?(current_version, candidate_version)
- Chef::Log.debug("#{new_resource} #{package_name} #{candidate_version} is already installed")
+ if current_version.nil?
+ # with use_magic_version there may be a package installed, but it fails the user's
+ # requested new_resource.version constraints
+ logger.trace("#{new_resource} has no existing installed version. Installing install #{candidate_version}")
+ target_version_array.push(candidate_version)
+ elsif version_equals?(current_version, new_version)
+ # this is a short-circuit to avoid needing to (expensively) query the candidate_version which must come later
+ logger.trace("#{new_resource} #{package_name} #{new_version} is already installed")
target_version_array.push(nil)
elsif candidate_version.nil?
- Chef::Log.debug("#{new_resource} #{package_name} has no candidate_version to upgrade to")
+ logger.trace("#{new_resource} #{package_name} has no candidate_version to upgrade to")
+ target_version_array.push(nil)
+ elsif version_equals?(current_version, candidate_version)
+ logger.trace("#{new_resource} #{package_name} #{candidate_version} is already installed")
+ target_version_array.push(nil)
+ elsif !allow_downgrade && version_compare(current_version, candidate_version) == 1
+ logger.trace("#{new_resource} #{package_name} has installed version #{current_version}, which is newer than available version #{candidate_version}. Skipping...)")
target_version_array.push(nil)
else
- Chef::Log.debug("#{new_resource} #{package_name} is out of date, will upgrade to #{candidate_version}")
+ logger.trace("#{new_resource} #{package_name} is out of date, will upgrade to #{candidate_version}")
target_version_array.push(candidate_version)
end
when :install
-
- if new_version
+ if new_version && !use_magic_version?
if version_requirement_satisfied?(current_version, new_version)
- Chef::Log.debug("#{new_resource} #{package_name} #{current_version} satisifies #{new_version} requirement")
+ logger.trace("#{new_resource} #{package_name} #{current_version} satisfies #{new_version} requirement")
+ target_version_array.push(nil)
+ elsif current_version && !allow_downgrade && version_compare(current_version, new_version) == 1
+ logger.warn("#{new_resource} #{package_name} has installed version #{current_version}, which is newer than available version #{new_version}. Skipping...)")
target_version_array.push(nil)
else
- Chef::Log.debug("#{new_resource} #{package_name} #{current_version} needs updating to #{new_version}")
+ logger.trace("#{new_resource} #{package_name} #{current_version} needs updating to #{new_version}")
target_version_array.push(new_version)
end
elsif current_version.nil?
- Chef::Log.debug("#{new_resource} #{package_name} not installed, installing #{candidate_version}")
+ # with use_magic_version there may be a package installed, but it fails the user's
+ # requested new_resource.version constraints
+ logger.trace("#{new_resource} #{package_name} not installed, installing #{candidate_version}")
target_version_array.push(candidate_version)
else
- Chef::Log.debug("#{new_resource} #{package_name} #{current_version} already installed")
+ logger.trace("#{new_resource} #{package_name} #{current_version} already installed")
target_version_array.push(nil)
end
@@ -472,8 +538,15 @@ class Chef
missing = []
each_package do |package_name, new_version, current_version, candidate_version|
next if new_version.nil? || current_version.nil?
- if !version_requirement_satisfied?(current_version, new_version) && candidate_version.nil?
- missing.push(package_name)
+
+ if use_magic_version?
+ if !magic_version && candidate_version.nil?
+ missing.push(package_name)
+ end
+ else
+ if !version_requirement_satisfied?(current_version, new_version) && candidate_version.nil?
+ missing.push(package_name)
+ end
end
end
missing
@@ -486,7 +559,7 @@ class Chef
def each_package
package_name_array.each_with_index do |package_name, i|
candidate_version = candidate_version_array[i]
- current_version = current_version_array[i]
+ current_version = use_magic_version? ? magic_version[i] : current_version_array[i]
new_version = new_version_array[i]
yield package_name, new_version, current_version, candidate_version
end
@@ -494,12 +567,12 @@ class Chef
# @return [Boolean] if we're doing a multipackage install or not
def multipackage?
- new_resource.package_name.is_a?(Array)
+ @multipackage_bool ||= new_resource.package_name.is_a?(Array)
end
# @return [Array] package_name(s) as an array
def package_name_array
- [ new_resource.package_name ].flatten
+ @package_name_array ||= [ new_resource.package_name ].flatten
end
# @return [Array] candidate_version(s) as an array
@@ -511,12 +584,12 @@ class Chef
# @return [Array] current_version(s) as an array
def current_version_array
- [ current_resource.version ].flatten
+ @current_version_array ||= [ current_resource.version ].flatten
end
# @return [Array] new_version(s) as an array
def new_version_array
- [ new_resource.version ].flatten.map { |v| v.to_s.empty? ? nil : v }
+ @new_version_array ||= [ 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
@@ -524,11 +597,14 @@ class Chef
#
# @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
+ @source_array ||=
+ begin
+ if new_resource.source.nil?
+ package_name_array.map { nil }
+ else
+ [ new_resource.source ].flatten
+ end
+ end
end
# Helper to handle use_package_name_for_source to convert names into local packages to install.
@@ -541,7 +617,7 @@ class Chef
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.")
+ logger.trace("No package source specified, but #{package_name} exists on filesystem, using #{package_name} as source.")
package_name
else
source
@@ -550,43 +626,12 @@ class Chef
end
end
- # @todo: extract apt/dpkg specific preseeding to a helper class
- def template_available?(path)
- run_context.has_template_in_cookbook?(new_resource.cookbook_name, path)
- end
-
- # @todo: extract apt/dpkg specific preseeding to a helper class
- def cookbook_file_available?(path)
- run_context.has_cookbook_file_in_cookbook?(new_resource.cookbook_name, path)
- end
-
def allow_downgrade
- if @new_resource.respond_to?("allow_downgrade")
- @new_resource.allow_downgrade
- else
- 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
+ if new_resource.respond_to?("allow_downgrade")
+ new_resource.allow_downgrade
else
- args << { :timeout => new_resource.timeout ? new_resource.timeout : 900 }
+ true
end
- args
end
end
end
diff --git a/lib/chef/provider/package/aix.rb b/lib/chef/provider/package/aix.rb
deleted file mode 100644
index 728f181055..0000000000
--- a/lib/chef/provider/package/aix.rb
+++ /dev/null
@@ -1,140 +0,0 @@
-#
-# Author:: Deepali Jagtap
-# Copyright:: Copyright 2013-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/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
-
- def define_resource_requirements
- super
- requirements.assert(:install) do |a|
- a.assertion { @new_resource.source }
- a.failure_message Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install"
- end
- requirements.assert(:all_actions) do |a|
- a.assertion { !@new_resource.source || @package_source_found }
- a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
- a.whyrun "would assume #{@new_resource.source} would be have previously been made available"
- end
- end
-
- def load_current_resource
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
-
- 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_with_timeout("installp -L -d #{@new_resource.source}")
- ret.stdout.each_line do |line|
- case line
- when /:#{@new_resource.package_name}:/
- fields = line.split(":")
- @new_resource.version(fields[2])
- when /^#{@new_resource.package_name}:/
- Chef::Log.warn("You are installing a bff package by product name. For idempotent installs, please install individual filesets")
- fields = line.split(":")
- @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_with_timeout("lslpp -lcq #{@current_resource.package_name}")
- ret.stdout.each_line do |line|
- case line
- when /#{@current_resource.package_name}/
- fields = line.split(":")
- Chef::Log.debug("#{@new_resource} version #{fields[2]} is already installed")
- @current_resource.version(fields[2])
- end
- end
-
- unless ret.exitstatus == 0 || ret.exitstatus == 1
- raise Chef::Exceptions::Package, "lslpp failed - #{ret.format_for_exception}!"
- end
-
- @current_resource
- end
-
- def candidate_version
- return @candidate_version if @candidate_version
- 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)}:(.*)/
- fields = line.split(":")
- @candidate_version = fields[2]
- @new_resource.version(fields[2])
- Chef::Log.debug("#{@new_resource} setting install candidate version to #{@candidate_version}")
- end
- end
- unless ret.exitstatus == 0
- raise Chef::Exceptions::Package, "installp -L -d #{@new_resource.source} - #{ret.format_for_exception}!"
- end
- @candidate_version
- end
-
- #
- # The install/update action needs to be tested with various kinds of packages
- # on AIX viz. packages with or without licensing file dependencies, packages
- # with dependencies on other packages which will help to test additional
- # options of installp.
- # So far, the code has been tested only with standalone packages.
- #
- def install_package(name, version)
- Chef::Log.debug("#{@new_resource} package install options: #{@new_resource.options}")
- if @new_resource.options.nil?
- 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_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
-
- alias_method :upgrade_package, :install_package
-
- def remove_package(name, version)
- if @new_resource.options.nil?
- shell_out_with_timeout!( "installp -u #{name}" )
- Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
- else
- shell_out_with_timeout!( "installp -u #{expand_options(@new_resource.options)} #{name}" )
- Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
- end
- end
-
- end
- end
- end
-end
diff --git a/lib/chef/provider/package/apt.rb b/lib/chef/provider/package/apt.rb
index 8af089e14a..dbacaedb07 100644
--- a/lib/chef/provider/package/apt.rb
+++ b/lib/chef/provider/package/apt.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,17 +16,19 @@
# limitations under the License.
#
-require "chef/provider/package"
-require "chef/resource/apt_package"
+require_relative "../package"
+require_relative "deb"
+require_relative "../../resource/apt_package"
class Chef
class Provider
class Package
class Apt < Chef::Provider::Package
+ include Chef::Provider::Package::Deb
use_multipackage_api
- provides :package, platform_family: "debian"
- provides :apt_package, os: "linux"
+ provides :package, platform_family: "debian", target_mode: true
+ provides :apt_package, target_mode: true
def initialize(new_resource, run_context)
super
@@ -44,7 +46,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 property. Use dpkg provider instead")
end
end
@@ -70,11 +72,28 @@ class Chef
@candidate_version ||= get_candidate_versions
end
+ def packages_all_locked?(names, versions)
+ names.all? { |n| locked_packages.include? n }
+ end
+
+ def packages_all_unlocked?(names, versions)
+ names.all? { |n| !locked_packages.include? n }
+ end
+
+ def locked_packages
+ @locked_packages ||=
+ begin
+ locked = shell_out!("apt-mark", "showhold")
+ locked.stdout.each_line.map(&:strip)
+ end
+ end
+
def install_package(name, version)
package_name = name.zip(version).map do |n, v|
package_data[n][:virtual] ? n : "#{n}=#{v}"
end
- run_noninteractive("apt-get -q -y", default_release_options, new_resource.options, "install", package_name)
+ dgrade = "--allow-downgrades" if supports_allow_downgrade? && allow_downgrade
+ run_noninteractive("apt-get", "-q", "-y", dgrade, config_file_options, default_release_options, options, "install", package_name)
end
def upgrade_package(name, version)
@@ -85,38 +104,73 @@ class Chef
package_name = name.map do |n|
package_data[n][:virtual] ? resolve_virtual_package_name(n) : n
end
- run_noninteractive("apt-get -q -y", new_resource.options, "remove", package_name)
+ run_noninteractive("apt-get", "-q", "-y", options, "remove", package_name)
end
def purge_package(name, version)
package_name = name.map do |n|
package_data[n][:virtual] ? resolve_virtual_package_name(n) : n
end
- run_noninteractive("apt-get -q -y", new_resource.options, "purge", package_name)
+ run_noninteractive("apt-get", "-q", "-y", options, "purge", package_name)
end
- def preseed_package(preseed_file)
- Chef::Log.info("#{new_resource} pre-seeding package installation instructions")
- run_noninteractive("debconf-set-selections", preseed_file)
+ def lock_package(name, version)
+ run_noninteractive("apt-mark", options, "hold", name)
end
- def reconfig_package(name, version)
- Chef::Log.info("#{new_resource} reconfiguring")
- run_noninteractive("dpkg-reconfigure", name)
+ def unlock_package(name, version)
+ run_noninteractive("apt-mark", options, "unhold", name)
end
private
- # Runs command via shell_out with magic environment to disable
- # interactive prompts. Command is run with default localization rather
- # than forcing locale to "C", so command output may not be stable.
- def run_noninteractive(*args)
- shell_out_with_timeout!(a_to_s(*args), :env => { "DEBIAN_FRONTEND" => "noninteractive" })
+ # @return [String] version of apt-get which is installed
+ def apt_version
+ @apt_version ||= shell_out("apt-get --version").stdout.match(/^apt (\S+)/)[1]
+ end
+
+ # @return [Boolean] if apt-get supports --allow-downgrades
+ def supports_allow_downgrade?
+ return @supports_allow_downgrade unless @supports_allow_downgrade.nil?
+
+ @supports_allow_downgrade = ( version_compare(apt_version, "1.1.0") >= 0 )
+ end
+
+ # compare 2 versions to each other to see which is newer.
+ # this differs from the standard package method because we
+ # need to be able to parse debian version strings which contain
+ # tildes which Gem cannot properly parse
+ #
+ # @return [Integer] 1 if v1 > v2. 0 if they're equal. -1 if v1 < v2
+ def version_compare(v1, v2)
+ if !shell_out("dpkg", "--compare-versions", v1.to_s, "gt", v2.to_s).error?
+ 1
+ elsif !shell_out("dpkg", "--compare-versions", v1.to_s, "eq", v2.to_s).error?
+ 0
+ else
+ -1
+ end
end
def default_release_options
# Use apt::Default-Release option only if provider supports it
- "-o APT::Default-Release=#{new_resource.default_release}" if new_resource.respond_to?(:default_release) && new_resource.default_release
+ if new_resource.respond_to?(:default_release) && new_resource.default_release
+ [ "-o", "APT::Default-Release=#{new_resource.default_release}" ]
+ end
+ end
+
+ def config_file_options
+ # If the user has specified config file options previously, respect those.
+ return if Array(options).any? { |opt| opt.include?("--force-conf") }
+
+ # It doesn't make sense to install packages in a scenario that can
+ # result in a prompt. Have users decide up-front whether they want to
+ # forcibly overwrite the config file, otherwise preserve it.
+ if new_resource.overwrite_config_files
+ [ "-o", "Dpkg::Options::=--force-confnew" ]
+ else
+ [ "-o", "Dpkg::Options::=--force-confdef", "-o", "Dpkg::Options::=--force-confold" ]
+ end
end
def resolve_package_versions(pkg)
@@ -126,19 +180,20 @@ class Chef
case line
when /^\s{2}Installed: (.+)$/
current_version = ( $1 != "(none)" ) ? $1 : nil
- Chef::Log.debug("#{new_resource} installed version for #{pkg} is #{$1}")
+ logger.trace("#{new_resource} installed version for #{pkg} is #{$1}")
when /^\s{2}Candidate: (.+)$/
candidate_version = ( $1 != "(none)" ) ? $1 : nil
- Chef::Log.debug("#{new_resource} candidate version for #{pkg} is #{$1}")
+ logger.trace("#{new_resource} candidate version for #{pkg} is #{$1}")
end
end
[ current_version, candidate_version ]
end
def resolve_virtual_package_name(pkg)
- showpkg = run_noninteractive("apt-cache showpkg", pkg).stdout
+ showpkg = run_noninteractive("apt-cache", "showpkg", pkg).stdout
partitions = showpkg.rpartition(/Reverse Provides: ?#{$/}/)
return nil if partitions[0] == "" && partitions[1] == "" # not found in output
+
set = partitions[2].lines.each_with_object(Set.new) do |line, acc|
# there may be multiple reverse provides for a single package
acc.add(line.split[0])
@@ -146,7 +201,8 @@ class Chef
if set.size > 1
raise Chef::Exceptions::Package, "#{new_resource.package_name} is a virtual package provided by multiple packages, you must explicitly select one"
end
- return set.to_a.first
+
+ set.to_a.first
end
def package_data_for(pkg)
@@ -161,15 +217,15 @@ class Chef
if newpkg
virtual = true
- Chef::Log.info("#{new_resource} is a virtual package, actually acting on package[#{newpkg}]")
+ logger.info("#{new_resource} is a virtual package, actually acting on package[#{newpkg}]")
current_version, candidate_version = resolve_package_versions(newpkg)
end
end
- return {
- current_version: current_version,
- candidate_version: candidate_version,
- virtual: virtual,
+ {
+ current_version: current_version,
+ candidate_version: candidate_version,
+ virtual: virtual,
}
end
diff --git a/lib/chef/provider/package/bff.rb b/lib/chef/provider/package/bff.rb
new file mode 100644
index 0000000000..430d3503b6
--- /dev/null
+++ b/lib/chef/provider/package/bff.rb
@@ -0,0 +1,143 @@
+#
+# Author:: Deepali Jagtap
+# Copyright:: Copyright (c) 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_relative "../package"
+require_relative "../../resource/package"
+require_relative "../../mixin/get_source_from_package"
+
+class Chef
+ class Provider
+ class Package
+ class Bff < Chef::Provider::Package
+
+ provides :package, os: "aix"
+ provides :bff_package
+
+ include Chef::Mixin::GetSourceFromPackage
+
+ def define_resource_requirements
+ super
+ requirements.assert(:install) do |a|
+ a.assertion { new_resource.source }
+ a.failure_message Chef::Exceptions::Package, "Source for package #{new_resource.package_name} required for action install"
+ end
+ requirements.assert(:all_actions) do |a|
+ a.assertion { !new_resource.source || package_source_found? }
+ a.failure_message Chef::Exceptions::Package, "Package #{new_resource.package_name} not found: #{new_resource.source}"
+ a.whyrun "would assume #{new_resource.source} would be have previously been made available"
+ end
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
+
+ if package_source_found?
+ logger.trace("#{new_resource} checking pkg status")
+ ret = shell_out("installp", "-L", "-d", new_resource.source)
+ ret.stdout.each_line do |line|
+ case line
+ when /:#{new_resource.package_name}:/
+ fields = line.split(":")
+ new_resource.version(fields[2])
+ when /^#{new_resource.package_name}:/
+ logger.warn("You are installing a bff package by product name. For idempotent installs, please install individual filesets")
+ fields = line.split(":")
+ 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
+
+ logger.trace("#{new_resource} checking install state")
+ ret = shell_out("lslpp", "-lcq", current_resource.package_name)
+ ret.stdout.each_line do |line|
+ case line
+ when /#{current_resource.package_name}/
+ fields = line.split(":")
+ logger.trace("#{new_resource} version #{fields[2]} is already installed")
+ current_resource.version(fields[2])
+ end
+ end
+
+ unless ret.exitstatus == 0 || ret.exitstatus == 1
+ raise Chef::Exceptions::Package, "lslpp failed - #{ret.format_for_exception}!"
+ end
+
+ current_resource
+ end
+
+ def candidate_version
+ return @candidate_version if @candidate_version
+
+ if package_source_found?
+ ret = shell_out("installp", "-L", "-d", new_resource.source)
+ ret.stdout.each_line do |line|
+ case line
+ when /\w:#{Regexp.escape(new_resource.package_name)}:(.*)/
+ fields = line.split(":")
+ @candidate_version = fields[2]
+ new_resource.version(fields[2])
+ logger.trace("#{new_resource} setting install candidate version to #{@candidate_version}")
+ end
+ end
+ unless ret.exitstatus == 0
+ raise Chef::Exceptions::Package, "installp -L -d #{new_resource.source} - #{ret.format_for_exception}!"
+ end
+ end
+ @candidate_version
+ end
+
+ #
+ # The install/update action needs to be tested with various kinds of packages
+ # on AIX viz. packages with or without licensing file dependencies, packages
+ # with dependencies on other packages which will help to test additional
+ # options of installp.
+ # So far, the code has been tested only with standalone packages.
+ #
+ def install_package(name, version)
+ logger.trace("#{new_resource} package install options: #{options}")
+ if options.nil?
+ shell_out!("installp", "-aYF", "-d", new_resource.source, new_resource.package_name)
+ logger.trace("#{new_resource} installed version #{new_resource.version} from: #{new_resource.source}")
+ else
+ shell_out!("installp", "-aYF", options, "-d", new_resource.source, new_resource.package_name)
+ logger.trace("#{new_resource} installed version #{new_resource.version} from: #{new_resource.source}")
+ end
+ end
+
+ alias upgrade_package install_package
+
+ def remove_package(name, version)
+ if options.nil?
+ shell_out!("installp", "-u", name)
+ logger.trace("#{new_resource} removed version #{new_resource.version}")
+ else
+ shell_out!("installp", "-u", options, name)
+ logger.trace("#{new_resource} removed version #{new_resource.version}")
+ end
+ end
+
+ def package_source_found?
+ @package_source_found ||= new_resource.source && ::File.exist?(new_resource.source)
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/cab.rb b/lib/chef/provider/package/cab.rb
new file mode 100644
index 0000000000..4aaa034fa4
--- /dev/null
+++ b/lib/chef/provider/package/cab.rb
@@ -0,0 +1,188 @@
+#
+# Author:: Vasundhara Jagdale (<vasundhara.jagdale@msystechnologies.com>)
+# Copyright:: Copyright (c) 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_relative "../package"
+require_relative "../../resource/cab_package"
+require_relative "../../mixin/shell_out"
+require_relative "../../mixin/uris"
+require_relative "../../mixin/checksum"
+require "cgi" unless defined?(CGI)
+
+class Chef
+ class Provider
+ class Package
+ class Cab < Chef::Provider::Package
+ include Chef::Mixin::ShellOut
+ include Chef::Mixin::Uris
+ include Chef::Mixin::Checksum
+
+ provides :cab_package, os: "windows"
+
+ def load_current_resource
+ @current_resource = Chef::Resource::CabPackage.new(new_resource.name)
+ current_resource.source(cab_file_source)
+ new_resource.version(package_version)
+ current_resource.version(installed_version)
+ current_resource
+ end
+
+ def cab_file_source
+ @cab_file_source ||= uri_scheme?(new_resource.source) ? download_source_file : new_resource.source
+ end
+
+ def download_source_file
+ source_resource.path
+ end
+
+ def source_resource
+ declare_resource(:remote_file, new_resource.name) do
+ path default_download_cache_path
+ source new_resource.source
+ backup false
+ end
+ end
+
+ def default_download_cache_path
+ uri = ::URI.parse(new_resource.source)
+ filename = ::File.basename(::CGI.unescape(uri.path))
+ file_cache_dir = Chef::FileCache.create_cache_path("package/")
+ Chef::Util::PathHelper.cleanpath("#{file_cache_dir}/#{filename}")
+ end
+
+ def install_package(name, version)
+ dism_command("/Add-Package /PackagePath:\"#{cab_file_source}\"")
+ end
+
+ def remove_package(name, version)
+ dism_command("/Remove-Package /PackagePath:\"#{cab_file_source}\"")
+ end
+
+ def dism_command(command)
+ with_os_architecture(nil) do
+ result = shell_out("dism.exe /Online /English #{command} /NoRestart", timeout: new_resource.timeout)
+ if result.exitstatus == -2146498530
+ raise Chef::Exceptions::Package, "The specified package is not applicable to this image." if result.stdout.include?("0x800f081e")
+
+ result.error!
+ end
+ result
+ end
+ end
+
+ def installed_version
+ # e.g. Package_for_KB2975719~31bf3856ad364e35~amd64~~6.3.1.8
+ package = new_cab_identity
+ # Search for just the package name to catch a different version being installed
+ logger.trace("#{new_resource} searching for installed package #{package["name"]}")
+ existing_package_identities = installed_packages.map do |p|
+ split_package_identity(p["package_identity"])
+ end
+ found_packages = existing_package_identities.select do |existing_package_ident|
+ existing_package_ident["version"] == package["version"].chomp && existing_package_ident["name"] == package["name"]
+ end
+ if found_packages.empty?
+ nil
+ elsif found_packages.length == 1
+ found_packages.first["version"]
+ else
+ # Presuming this won't happen, otherwise we need to handle it
+ raise Chef::Exceptions::Package, "Found multiple packages installed matching name #{package["name"]}, found: #{found_packages.length} matches"
+ end
+ end
+
+ def cab_identity_from_cab_file
+ stdout = dism_command("/Get-PackageInfo /PackagePath:\"#{cab_file_source}\"").stdout
+ package_info = parse_dism_get_package_info(stdout)
+ split_package_identity(package_info["package_information"]["package_identity"])
+ end
+
+ def new_cab_identity
+ logger.trace("#{new_resource} getting product version for package at #{cab_file_source}")
+ @new_cab_identity ||= cab_identity_from_cab_file
+ end
+
+ def package_version
+ new_cab_identity["version"].chomp
+ end
+
+ # returns a hash of package state information given the output of dism /get-packages
+ # expected keys: package_identity
+ def parse_dism_get_packages(text)
+ packages = []
+ text.each_line do |line|
+ key, value = line.split(":") if line.start_with?("Package Identity")
+ next if key.nil? || value.nil?
+
+ package = {}
+ package[key.downcase.strip.tr(" ", "_")] = value.strip.chomp
+ packages << package
+ end
+ packages
+ end
+
+ # returns a hash of package information given the output of dism /get-packageinfo
+ def parse_dism_get_package_info(text)
+ package_data = {}
+ errors = []
+ in_section = false
+ section_headers = [ "Package information", "Custom Properties", "Features" ]
+ text.each_line do |line|
+ if line =~ /Error: (.*)/
+ errors << $1.strip
+ elsif section_headers.any? { |header| line =~ /^(#{header})/ }
+ in_section = $1.downcase.tr(" ", "_")
+ elsif line =~ /(.*) ?: (.*)/
+ v = $2 # has to be first or the gsub below replaces this variable
+ k = $1.downcase.strip.tr(" ", "_")
+ if in_section
+ package_data[in_section] = {} unless package_data[in_section]
+ package_data[in_section][k] = v
+ else
+ package_data[k] = v
+ end
+ end
+ end
+ unless errors.empty?
+ if errors.include?("0x80070003") || errors.include?("0x80070002")
+ raise Chef::Exceptions::Package, "DISM: The system cannot find the path or file specified."
+ elsif errors.include?("740")
+ raise Chef::Exceptions::Package, "DISM: Error 740: Elevated permissions are required to run DISM."
+ else
+ raise Chef::Exceptions::Package, "Unknown errors encountered parsing DISM output: #{errors}"
+ end
+ end
+ package_data
+ end
+
+ def split_package_identity(identity)
+ data = {}
+ data["name"], data["publisher"], data["arch"], data["resource_id"], data["version"] = identity.split("~")
+ data
+ end
+
+ def installed_packages
+ @packages ||= begin
+ output = dism_command("/Get-Packages").stdout
+ packages = parse_dism_get_packages(output)
+ packages
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/chocolatey.rb b/lib/chef/provider/package/chocolatey.rb
index 36bc170590..06db3c2979 100644
--- a/lib/chef/provider/package/chocolatey.rb
+++ b/lib/chef/provider/package/chocolatey.rb
@@ -1,5 +1,5 @@
#
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,29 +15,28 @@
# limitations under the License.
#
-require "chef/provider/package"
-require "chef/resource/chocolatey_package"
-require "chef/mixin/powershell_out"
+require_relative "../package"
+require_relative "../../resource/chocolatey_package"
+require_relative "../../win32/api/command_line_helper" if ChefUtils.windows?
class Chef
class Provider
class Package
class Chocolatey < Chef::Provider::Package
- include Chef::Mixin::PowershellOut
-
- provides :chocolatey_package, os: "windows"
+ include Chef::ReservedNames::Win32::API::CommandLineHelper if ChefUtils.windows?
+ provides :chocolatey_package
# Declare that our arguments should be arrays
use_multipackage_api
- PATHFINDING_POWERSHELL_COMMAND = "[System.Environment]::GetEnvironmentVariable('ChocolateyInstall', 'MACHINE')"
- CHOCO_MISSING_MSG = <<-EOS
-Could not locate your Chocolatey install. To install chocolatey, we recommend
-the 'chocolatey' cookbook (https://github.com/chocolatey/chocolatey-cookbook).
-If Chocolatey is installed, ensure that the 'ChocolateyInstall' environment
-variable is correctly set. You can verify this with the PowerShell command
-'#{PATHFINDING_POWERSHELL_COMMAND}'.
-EOS
+ PATHFINDING_POWERSHELL_COMMAND = "[System.Environment]::GetEnvironmentVariable('ChocolateyInstall', 'MACHINE')".freeze
+ CHOCO_MISSING_MSG = <<~EOS.freeze
+ Could not locate your Chocolatey install. To install chocolatey, we recommend
+ the 'chocolatey' cookbook (https://github.com/chocolatey/chocolatey-cookbook).
+ If Chocolatey is installed, ensure that the 'ChocolateyInstall' environment
+ variable is correctly set. You can verify this with the PowerShell command
+ '#{PATHFINDING_POWERSHELL_COMMAND}'.
+ EOS
# Responsible for building the current_resource.
#
@@ -54,7 +53,7 @@ EOS
# The check that Chocolatey is installed is in #choco_exe.
- # Chocolatey source attribute points to an alternate feed
+ # Chocolatey source property 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|
@@ -80,17 +79,17 @@ EOS
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? }
+ name_has_versions = name_versions_to_install.compact
# 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)
+ 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)
+ choco_command("install", "-y", cmd_args, *cmd_names)
end
end
@@ -102,17 +101,17 @@ EOS
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? }
+ name_has_versions = name_versions_to_install.compact
# 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)
+ 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)
+ choco_command("upgrade", "-y", cmd_args, *cmd_names)
end
end
@@ -121,27 +120,29 @@ EOS
# @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(include_source: false), *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
+ choco_command("uninstall", "-y", cmd_args(include_source: false), *names)
end
# Choco does not have dpkg's distinction between purge and remove
- alias_method :purge_package, :remove_package
+ alias 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
+ def check_resource_semantics!; end
private
+ def version_compare(v1, v2)
+ if v1 == "latest" || v2 == "latest"
+ return 0
+ end
+
+ gem_v1 = Gem::Version.new(v1)
+ gem_v2 = Gem::Version.new(v2)
+
+ gem_v1 <=> gem_v2
+ end
+
# Magic to find where chocolatey is installed in the system, and to
# return the full path of choco.exe
#
@@ -150,17 +151,18 @@ EOS
@choco_exe ||= begin
# if this check is in #define_resource_requirements, it won't get
# run before choco.exe gets called from #load_current_resource.
- exe_path = ::File.join(choco_install_path.to_s, "bin", "choco.exe")
+ exe_path = ::File.join(choco_install_path, "bin", "choco.exe")
raise Chef::Exceptions::MissingLibrary, CHOCO_MISSING_MSG unless ::File.exist?(exe_path)
+
exe_path
end
end
# lets us mock out an incorrect value for testing.
def choco_install_path
- @choco_install_path ||= powershell_out!(
- PATHFINDING_POWERSHELL_COMMAND
- ).stdout.chomp
+ result = powershell_exec!(PATHFINDING_POWERSHELL_COMMAND).result
+ result = "" if result.empty?
+ result
end
# Helper to dispatch a choco command through shell_out using the timeout
@@ -169,7 +171,7 @@ EOS
# @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))
+ shell_out!(choco_exe, *args, returns: new_resource.returns)
end
# Use the available_packages Hash helper to create an array suitable for
@@ -206,19 +208,9 @@ EOS
# @param include_source [Boolean] should the source parameter be added
# @return [String] options from new_resource or empty string
def cmd_args(include_source: true)
- cmd_args = [ new_resource.options ]
- cmd_args.push( "-source #{new_resource.source}" ) if new_resource.source && include_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(" ")
+ cmd_args = new_resource.options.is_a?(String) ? command_line_to_argv_w_helper(new_resource.options) : Array(new_resource.options)
+ cmd_args += common_options(include_source: include_source)
+ cmd_args
end
# Available packages in chocolatey as a Hash of names mapped to versions
@@ -227,15 +219,24 @@ EOS
#
# @return [Hash] name-to-version mapping of available packages
def available_packages
- @available_packages ||=
- begin
- cmd = [ "list -r #{package_name_array.join ' '}" ]
- cmd.push( "-source #{new_resource.source}" ) if new_resource.source
- raw = parse_list_output(*cmd)
- raw.keys.each_with_object({}) do |name, available|
- available[name] = desired_name_versions[name] || raw[name]
+ return @available_packages if @available_packages
+
+ @available_packages = {}
+ package_name_array.each do |pkg|
+ available_versions =
+ begin
+ cmd = [ "list", "-r", pkg ]
+ cmd += common_options
+ cmd.push( new_resource.list_options ) if new_resource.list_options
+
+ raw = parse_list_output(*cmd)
+ raw.keys.each_with_object({}) do |name, available|
+ available[name] = desired_name_versions[name] || raw[name]
+ end
end
- end
+ @available_packages.merge! available_versions
+ end
+ @available_packages
end
# Installed packages in chocolatey as a Hash of names mapped to versions
@@ -243,11 +244,12 @@ EOS
#
# @return [Hash] name-to-version mapping of installed packages
def installed_packages
- @installed_packages ||= Hash[*parse_list_output("list -l -r").flatten]
+ @installed_packages ||= Hash[*parse_list_output("list", "-l", "-r").flatten]
+ @installed_packages
end
# Helper to convert choco.exe list output to a Hash
- # (names are downcased for case-insenstive matching)
+ # (names are downcased for case-insensitive matching)
#
# @param cmd [String] command to run
# @return [Hash] list output converted to ruby Hash
@@ -255,8 +257,9 @@ EOS
hash = {}
choco_command(*args).stdout.each_line do |line|
next if line.start_with?("Chocolatey v")
+
name, version = line.split("|")
- hash[name.downcase] = version.chomp
+ hash[name.downcase] = version&.chomp
end
hash
end
@@ -266,7 +269,15 @@ EOS
# @param names [Array] original mixed case names
# @return [Array] same names in lower case
def lowercase_names(names)
- names.map { |name| name.downcase }
+ names.map(&:downcase)
+ end
+
+ def common_options(include_source: true)
+ args = []
+ args.push( [ "-source", new_resource.source ] ) if new_resource.source && include_source
+ args.push( [ "--user", new_resource.user ] ) if new_resource.user
+ args.push( [ "--password", new_resource.password ]) if new_resource.password
+ args
end
end
end
diff --git a/lib/chef/provider/package/deb.rb b/lib/chef/provider/package/deb.rb
new file mode 100644
index 0000000000..1b1b151c93
--- /dev/null
+++ b/lib/chef/provider/package/deb.rb
@@ -0,0 +1,131 @@
+#
+# Author:: Kapil Chouhan (<kapil.chouhan@msystechnologies.com>)
+# Copyright:: Copyright (c) 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_relative "../package"
+
+class Chef
+ class Provider
+ class Package
+ module Deb
+ def self.included(base)
+ base.class_eval do
+ use_multipackage_api
+
+ action :reconfig do
+ if current_resource.version.nil?
+ logger.trace("#{new_resource} is NOT installed - nothing to do")
+ return
+ end
+
+ unless new_resource.response_file
+ logger.trace("#{new_resource} no response_file provided - nothing to do")
+ return
+ end
+
+ 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)
+ multipackage_api_adapter(new_resource.package_name, current_resource.version) do |name, _version|
+ reconfig_package(name)
+ end
+ logger.info("#{new_resource} reconfigured")
+ end
+ else
+ logger.trace("#{new_resource} preseeding has not changed - nothing to do")
+ end
+ end
+
+ # This method is used for getting preseed file
+ # it will return preseed file path or false if response_file is present
+ def prepare_for_installation
+ if new_resource.response_file && preseed_file = get_preseed_file(package_names_for_targets, versions_for_targets)
+ converge_by("preseed package #{package_names_for_targets}") do
+ preseed_package(preseed_file)
+ end
+ end
+ end
+
+ def get_preseed_file(name, version)
+ resource = preseed_resource(name, version)
+ resource.run_action(:create)
+ logger.trace("#{new_resource} fetched preseed file to #{resource.path}")
+
+ if resource.updated_by_last_action?
+ resource.path
+ else
+ false
+ end
+ end
+
+ def preseed_resource(name, version)
+ # A directory in our cache to store this cookbook's preseed files in
+ file_cache_dir = Chef::FileCache.create_cache_path("preseed/#{new_resource.cookbook_name}")
+ # The full path where the preseed file will be stored
+ cache_seed_to = "#{file_cache_dir}/#{name}-#{version}.seed"
+
+ logger.trace("#{new_resource} fetching preseed file to #{cache_seed_to}")
+
+ if template_available?(new_resource.response_file)
+ logger.trace("#{new_resource} fetching preseed file via Template")
+ remote_file = Chef::Resource::Template.new(cache_seed_to, run_context)
+ remote_file.variables(new_resource.response_file_variables)
+ elsif cookbook_file_available?(new_resource.response_file)
+ logger.trace("#{new_resource} fetching preseed file via cookbook_file")
+ remote_file = Chef::Resource::CookbookFile.new(cache_seed_to, run_context)
+ else
+ message = "No template or cookbook file found for response file #{new_resource.response_file}"
+ raise Chef::Exceptions::FileNotFound, message
+ end
+
+ remote_file.cookbook_name = new_resource.cookbook_name
+ remote_file.source(new_resource.response_file)
+ remote_file.backup(false)
+ remote_file
+ end
+
+ def preseed_package(preseed_file)
+ logger.info("#{new_resource} pre-seeding package installation instructions")
+ run_noninteractive("debconf-set-selections", preseed_file)
+ end
+
+ def reconfig_package(name)
+ logger.info("#{new_resource} reconfiguring")
+ run_noninteractive("dpkg-reconfigure", *name)
+ end
+
+ # Runs command via shell_out with magic environment to disable
+ # interactive prompts.
+ def run_noninteractive(*command)
+ shell_out!(*command, env: { "DEBIAN_FRONTEND" => "noninteractive" })
+ end
+
+ private
+
+ def template_available?(path)
+ run_context.has_template_in_cookbook?(new_resource.cookbook_name, path)
+ end
+
+ def cookbook_file_available?(path)
+ run_context.has_cookbook_file_in_cookbook?(new_resource.cookbook_name, path)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/dnf.rb b/lib/chef/provider/package/dnf.rb
new file mode 100644
index 0000000000..5c74ad0414
--- /dev/null
+++ b/lib/chef/provider/package/dnf.rb
@@ -0,0 +1,279 @@
+#
+# Copyright:: Copyright (c) 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_relative "../package"
+require_relative "../../resource/dnf_package"
+require_relative "../../mixin/which"
+require_relative "../../mixin/shell_out"
+require_relative "../../mixin/get_source_from_package"
+require_relative "dnf/python_helper"
+require_relative "dnf/version"
+
+class Chef
+ class Provider
+ class Package
+ class Dnf < Chef::Provider::Package
+ extend Chef::Mixin::Which
+ extend Chef::Mixin::ShellOut
+ include Chef::Mixin::GetSourceFromPackage
+
+ allow_nils
+ use_multipackage_api
+ use_package_name_for_source
+ use_magic_version
+
+ # all rhel variants >= 8 will use DNF
+ provides :package, platform_family: "rhel", platform_version: ">= 8"
+
+ # fedora >= 22 uses DNF
+ provides :package, platform: "fedora", platform_version: ">= 22"
+
+ # amazon will eventually use DNF
+ provides :package, platform: "amazon" do
+ which("dnf")
+ end
+
+ provides :dnf_package
+
+ #
+ # Most of the magic in this class happens in the python helper script. The ruby side of this
+ # provider knows only enough to translate Chef-style new_resource name+package+version into
+ # a request to the python side. The python side is then responsible for knowing everything
+ # about RPMs and what is installed and what is available. The ruby side of this class should
+ # remain a lightweight translation layer to translate Chef requests into RPC requests to
+ # python. This class knows nothing about how to compare RPM versions, and does not maintain
+ # any cached state of installed/available versions and should be kept that way.
+ #
+ def python_helper
+ @python_helper ||= PythonHelper.instance
+ end
+
+ def load_current_resource
+ flushcache if new_resource.flush_cache[:before]
+
+ @current_resource = Chef::Resource::DnfPackage.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
+ current_resource.version(get_current_versions)
+
+ current_resource
+ end
+
+ def load_after_resource
+ # force the installed version array to repopulate
+ @current_version = []
+ @after_resource = Chef::Resource::DnfPackage.new(new_resource.name)
+ after_resource.package_name(new_resource.package_name)
+ after_resource.version(get_current_versions)
+
+ after_resource
+ end
+
+ def define_resource_requirements
+ requirements.assert(:install, :upgrade, :remove, :purge) do |a|
+ a.assertion { !new_resource.source || ::File.exist?(new_resource.source) }
+ a.failure_message Chef::Exceptions::Package, "Package #{new_resource.package_name} not found: #{new_resource.source}"
+ a.whyrun "assuming #{new_resource.source} would have previously been created"
+ end
+
+ super
+ end
+
+ def candidate_version
+ package_name_array.each_with_index.map do |pkg, i|
+ available_version(i).version_with_arch
+ end
+ end
+
+ def magic_version
+ package_name_array.each_with_index.map do |pkg, i|
+ magical_version(i).version_with_arch
+ end
+ end
+
+ def get_current_versions
+ package_name_array.each_with_index.map do |pkg, i|
+ current_version(i).version_with_arch
+ end
+ end
+
+ def install_package(names, versions)
+ if new_resource.source
+ dnf(options, "-y", "install", new_resource.source)
+ else
+ resolved_names = names.each_with_index.map { |name, i| available_version(i).to_s unless name.nil? }
+ dnf(options, "-y", "install", resolved_names)
+ end
+ flushcache
+ end
+
+ # dnf upgrade does not work on uninstalled packaged, while install will upgrade
+ alias upgrade_package install_package
+
+ def remove_package(names, versions)
+ resolved_names = names.each_with_index.map { |name, i| magical_version(i).to_s unless name.nil? }
+ dnf(options, "-y", "remove", resolved_names)
+ flushcache
+ end
+
+ alias purge_package remove_package
+
+ action :flush_cache do
+ flushcache
+ end
+
+ # NB: the dnf_package provider manages individual single packages, please do not submit issues or PRs to try to add wildcard
+ # support to lock / unlock. The best solution is to write an execute resource which does a not_if `dnf versionlock | grep '^pattern`` kind of approach
+ def lock_package(names, versions)
+ dnf("-d0", "-e0", "-y", options, "versionlock", "add", resolved_package_lock_names(names))
+ end
+
+ # NB: the dnf_package provider manages individual single packages, please do not submit issues or PRs to try to add wildcard
+ # support to lock / unlock. The best solution is to write an execute resource which does a only_if `dnf versionlock | grep '^pattern`` kind of approach
+ def unlock_package(names, versions)
+ # dnf versionlock delete on rhel6 needs the glob nonsense in the following command
+ dnf("-d0", "-e0", "-y", options, "versionlock", "delete", resolved_package_lock_names(names).map { |n| "*:#{n}-*" })
+ end
+
+ private
+
+ # this will resolve things like `/usr/bin/perl` or virtual packages like `mysql` -- it will not work (well? at all?) with globs that match multiple packages
+ def resolved_package_lock_names(names)
+ names.each_with_index.map do |name, i|
+ unless name.nil?
+ if magical_version(i).version.nil?
+ available_version(i).name
+ else
+ magical_version(i).name
+ end
+ end
+ end
+ end
+
+ def locked_packages
+ @locked_packages ||=
+ begin
+ locked = dnf("versionlock", "list")
+ locked.stdout.each_line.map do |line|
+ line.sub(/-[^-]*-[^-]*$/, "").split(":").last.strip
+ end
+ end
+ end
+
+ def packages_all_locked?(names, versions)
+ resolved_package_lock_names(names).all? { |n| locked_packages.include? n }
+ end
+
+ def packages_all_unlocked?(names, versions)
+ !resolved_package_lock_names(names).any? { |n| locked_packages.include? n }
+ end
+
+ def version_gt?(v1, v2)
+ return false if v1.nil? || v2.nil?
+
+ python_helper.compare_versions(v1, v2) == 1
+ end
+
+ def version_equals?(v1, v2)
+ return false if v1.nil? || v2.nil?
+
+ python_helper.compare_versions(v1, v2) == 0
+ end
+
+ def version_compare(v1, v2)
+ python_helper.compare_versions(v1, v2)
+ end
+
+ def resolve_source_to_version_obj
+ shell_out!("rpm -qp --queryformat '%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n' #{new_resource.source}").stdout.each_line do |line|
+ # this is another case of committing the sin of doing some lightweight mangling of RPM versions in ruby -- but the output of the rpm command
+ # does not match what the yum library accepts.
+ case line
+ when /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$/
+ return Version.new($1, "#{$2 == "(none)" ? "0" : $2}:#{$3}-#{$4}", $5)
+ end
+ end
+ end
+
+ # @return Array<Version>
+ def available_version(index)
+ @available_version ||= []
+
+ @available_version[index] ||= if new_resource.source
+ resolve_source_to_version_obj
+ else
+ python_helper.package_query(:whatavailable, package_name_array[index], version: safe_version_array[index], arch: safe_arch_array[index], options: options)
+ end
+
+ @available_version[index]
+ end
+
+ # @return [Array<Version>]
+ def magical_version(index)
+ @magical_version ||= []
+ @magical_version[index] ||= if new_resource.source
+ python_helper.package_query(:whatinstalled, available_version(index).name, version: safe_version_array[index], arch: safe_arch_array[index], options: options)
+ else
+ python_helper.package_query(:whatinstalled, package_name_array[index], version: safe_version_array[index], arch: safe_arch_array[index], options: options)
+ end
+ @magical_version[index]
+ end
+
+ def current_version(index)
+ @current_version ||= []
+ @current_version[index] ||= if new_resource.source
+ python_helper.package_query(:whatinstalled, available_version(index).name, arch: safe_arch_array[index], options: options)
+ else
+ python_helper.package_query(:whatinstalled, package_name_array[index], arch: safe_arch_array[index], options: options)
+ end
+ @current_version[index]
+ end
+
+ # cache flushing is accomplished by simply restarting the python helper. this produces a roughly
+ # 15% hit to the runtime of installing/removing/upgrading packages. correctly using multipackage
+ # array installs (and the multipackage cookbook) can produce 600% improvements in runtime.
+ def flushcache
+ python_helper.restart
+ end
+
+ def dnf(*args)
+ shell_out!("dnf", *args)
+ end
+
+ def safe_version_array
+ if new_resource.version.is_a?(Array)
+ new_resource.version
+ elsif new_resource.version.nil?
+ package_name_array.map { nil }
+ else
+ [ new_resource.version ]
+ end
+ end
+
+ def safe_arch_array
+ if new_resource.arch.is_a?(Array)
+ new_resource.arch
+ elsif new_resource.arch.nil?
+ package_name_array.map { nil }
+ else
+ [ new_resource.arch ]
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/dnf/dnf_helper.py b/lib/chef/provider/package/dnf/dnf_helper.py
new file mode 100644
index 0000000000..1dc797a643
--- /dev/null
+++ b/lib/chef/provider/package/dnf/dnf_helper.py
@@ -0,0 +1,186 @@
+#!/usr/bin/env python3
+# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
+
+import sys
+import dnf
+import hawkey
+import signal
+import os
+import json
+
+base = None
+
+def get_sack():
+ global base
+ if base is None:
+ base = dnf.Base()
+ conf = base.conf
+ conf.read()
+ conf.installroot = '/'
+ conf.assumeyes = True
+ subst = conf.substitutions
+ subst.update_from_etc(conf.installroot)
+ try:
+ base.init_plugins()
+ base.pre_configure_plugins()
+ except AttributeError:
+ pass
+ base.read_all_repos()
+ repos = base.repos
+
+ if 'repos' in command:
+ for repo_pattern in command['repos']:
+ if 'enable' in repo_pattern:
+ for repo in repos.get_matching(repo_pattern['enable']):
+ repo.enable()
+ if 'disable' in repo_pattern:
+ for repo in repos.get_matching(repo_pattern['disable']):
+ repo.disable()
+
+ try:
+ base.configure_plugins()
+ except AttributeError:
+ pass
+ base.fill_sack(load_system_repo='auto')
+ return base.sack
+
+# FIXME: leaks memory and does not work
+def flushcache():
+ try:
+ os.remove('/var/cache/dnf/@System.solv')
+ except OSError:
+ pass
+ get_sack().load_system_repo(build_cache=True)
+
+def version_tuple(versionstr):
+ e = '0'
+ v = None
+ r = None
+ colon_index = versionstr.find(':')
+ if colon_index > 0:
+ e = str(versionstr[:colon_index])
+ dash_index = versionstr.find('-')
+ if dash_index > 0:
+ tmp = versionstr[colon_index + 1:dash_index]
+ if tmp != '':
+ v = tmp
+ arch_index = versionstr.find('.', dash_index)
+ if arch_index > 0:
+ r = versionstr[dash_index + 1:arch_index]
+ else:
+ r = versionstr[dash_index + 1:]
+ else:
+ tmp = versionstr[colon_index + 1:]
+ if tmp != '':
+ v = tmp
+ return (e, v, r)
+
+def versioncompare(versions):
+ sack = get_sack()
+ if (versions[0] is None) or (versions[1] is None):
+ outpipe.write('0\n')
+ outpipe.flush()
+ else:
+ evr_comparison = dnf.rpm.rpm.labelCompare(version_tuple(versions[0]), version_tuple(versions[1]))
+ outpipe.write('{}\n'.format(evr_comparison))
+ outpipe.flush()
+
+def query(command):
+ sack = get_sack()
+
+ subj = dnf.subject.Subject(command['provides'])
+ q = subj.get_best_query(sack, with_provides=True)
+
+ if command['action'] == "whatinstalled":
+ q = q.installed()
+
+ if command['action'] == "whatavailable":
+ q = q.available()
+
+ if 'epoch' in command:
+ # We assume that any glob is "*" so just omit the filter since the dnf libraries have no
+ # epoch__glob filter. That means "?" wildcards in epochs will fail. The workaround is to
+ # not use the version filter here but to put the version with all the globs in the package name.
+ if not dnf.util.is_glob_pattern(command['epoch']):
+ q = q.filterm(epoch=int(command['epoch']))
+ if 'version' in command:
+ if dnf.util.is_glob_pattern(command['version']):
+ q = q.filterm(version__glob=command['version'])
+ else:
+ q = q.filterm(version=command['version'])
+ if 'release' in command:
+ if dnf.util.is_glob_pattern(command['release']):
+ q = q.filterm(release__glob=command['release'])
+ else:
+ q = q.filterm(release=command['release'])
+
+ if 'arch' in command:
+ if dnf.util.is_glob_pattern(command['arch']):
+ q = q.filterm(arch__glob=command['arch'])
+ else:
+ q = q.filterm(arch=command['arch'])
+
+ # only apply the default arch query filter if it returns something
+ archq = q.filter(arch=[ 'noarch', hawkey.detect_arch() ])
+ if len(archq.run()) > 0:
+ q = archq
+
+ pkgs = q.latest(1).run()
+
+ if not pkgs:
+ outpipe.write('{} nil nil\n'.format(command['provides'].split().pop(0)))
+ outpipe.flush()
+ else:
+ # make sure we picked the package with the highest version
+ pkgs.sort
+ pkg = pkgs.pop()
+ outpipe.write('{} {}:{}-{} {}\n'.format(pkg.name, pkg.epoch, pkg.version, pkg.release, pkg.arch))
+ outpipe.flush()
+
+# the design of this helper is that it should try to be 'brittle' and fail hard and exit in order
+# to keep process tables clean. additional error handling should probably be added to the retry loop
+# on the ruby side.
+def exit_handler(signal, frame):
+ if base is not None:
+ base.close()
+ sys.exit(0)
+
+def setup_exit_handler():
+ signal.signal(signal.SIGINT, exit_handler)
+ signal.signal(signal.SIGHUP, exit_handler)
+ signal.signal(signal.SIGPIPE, exit_handler)
+ signal.signal(signal.SIGQUIT, exit_handler)
+
+if len(sys.argv) < 3:
+ inpipe = sys.stdin
+ outpipe = sys.stdout
+else:
+ inpipe = os.fdopen(int(sys.argv[1]), "r")
+ outpipe = os.fdopen(int(sys.argv[2]), "w")
+
+try:
+ while 1:
+ # kill self if we get orphaned (tragic)
+ ppid = os.getppid()
+ if ppid == 1:
+ raise RuntimeError("orphaned")
+
+ setup_exit_handler()
+ line = inpipe.readline()
+
+ try:
+ command = json.loads(line)
+ except ValueError:
+ raise RuntimeError("bad json parse")
+
+ if command['action'] == "whatinstalled":
+ query(command)
+ elif command['action'] == "whatavailable":
+ query(command)
+ elif command['action'] == "versioncompare":
+ versioncompare(command['versions'])
+ else:
+ raise RuntimeError("bad command")
+finally:
+ if base is not None:
+ base.close()
diff --git a/lib/chef/provider/package/dnf/python_helper.rb b/lib/chef/provider/package/dnf/python_helper.rb
new file mode 100644
index 0000000000..1f6243b9b2
--- /dev/null
+++ b/lib/chef/provider/package/dnf/python_helper.rb
@@ -0,0 +1,220 @@
+#
+# Copyright:: Copyright (c) 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_relative "../../../mixin/which"
+require_relative "../../../mixin/shell_out"
+require_relative "version"
+require "singleton" unless defined?(Singleton)
+require "timeout" unless defined?(Timeout)
+
+class Chef
+ class Provider
+ class Package
+ class Dnf < Chef::Provider::Package
+ class PythonHelper
+ include Singleton
+ include Chef::Mixin::Which
+ include Chef::Mixin::ShellOut
+
+ attr_accessor :stdin
+ attr_accessor :stdout
+ attr_accessor :stderr
+ attr_accessor :inpipe
+ attr_accessor :outpipe
+ attr_accessor :wait_thr
+
+ DNF_HELPER = ::File.expand_path(::File.join(::File.dirname(__FILE__), "dnf_helper.py")).freeze
+
+ def dnf_command
+ # platform-python is used for system tools on RHEL 8 and is installed under /usr/libexec
+ @dnf_command ||= begin
+ cmd = which("platform-python", "python", "python3", "python2", "python2.7", extra_path: "/usr/libexec") do |f|
+ shell_out("#{f} -c 'import dnf'").exitstatus == 0
+ end
+ raise Chef::Exceptions::Package, "cannot find dnf libraries, you may need to use yum_package" unless cmd
+
+ "#{cmd} #{DNF_HELPER}"
+ end
+ end
+
+ def start
+ ENV["PYTHONUNBUFFERED"] = "1"
+ @inpipe, inpipe_write = IO.pipe
+ outpipe_read, @outpipe = IO.pipe
+ @stdin, @stdout, @stderr, @wait_thr = Open3.popen3("#{dnf_command} #{outpipe_read.fileno} #{inpipe_write.fileno}", outpipe_read.fileno => outpipe_read, inpipe_write.fileno => inpipe_write, close_others: false)
+ outpipe_read.close
+ inpipe_write.close
+ end
+
+ def reap
+ unless wait_thr.nil?
+ Process.kill("INT", wait_thr.pid) rescue nil
+ begin
+ Timeout.timeout(3) do
+ wait_thr.value # this calls waitpid()
+ end
+ rescue Timeout::Error
+ Process.kill("KILL", wait_thr.pid) rescue nil
+ end
+ stdin.close unless stdin.nil?
+ stdout.close unless stdout.nil?
+ stderr.close unless stderr.nil?
+ inpipe.close unless inpipe.nil?
+ outpipe.close unless outpipe.nil?
+ end
+ end
+
+ def check
+ start if stdin.nil?
+ end
+
+ def compare_versions(version1, version2)
+ query("versioncompare", { "versions" => [version1, version2] }).to_i
+ end
+
+ def options_params(options)
+ options.each_with_object({}) do |opt, h|
+ if opt =~ /--enablerepo=(.+)/
+ $1.split(",").each do |repo|
+ h["repos"] ||= []
+ h["repos"].push( { "enable" => repo } )
+ end
+ end
+ if opt =~ /--disablerepo=(.+)/
+ $1.split(",").each do |repo|
+ h["repos"] ||= []
+ h["repos"].push( { "disable" => repo } )
+ end
+ end
+ end
+ end
+
+ # @return Array<Version>
+ # NB: "options" here is the dnf_package options hash and is deliberately not **opts
+ def package_query(action, provides, version: nil, arch: nil, options: {})
+ parameters = { "provides" => provides, "version" => version, "arch" => arch }
+ repo_opts = options_params(options || {})
+ parameters.merge!(repo_opts)
+ # XXX: for now we restart before and after every query with an enablerepo/disablerepo to clean the helpers internal state
+ restart unless repo_opts.empty?
+ query_output = query(action, parameters)
+ version = parse_response(query_output.lines.last)
+ Chef::Log.trace "parsed #{version} from python helper"
+ restart unless repo_opts.empty?
+ version
+ end
+
+ def restart
+ reap
+ start
+ end
+
+ private
+
+ # i couldn't figure out how to decompose an evr on the python side, it seems reasonably
+ # painless to do it in ruby (generally massaging nevras in the ruby side is HIGHLY
+ # discouraged -- this is an "every rule has an exception" exception -- any additional
+ # functionality should probably trigger moving this regexp logic into python)
+ def add_version(hash, version)
+ epoch = nil
+ if version =~ /(\S+):(\S+)/
+ epoch = $1
+ version = $2
+ end
+ if version =~ /(\S+)-(\S+)/
+ version = $1
+ release = $2
+ end
+ hash["epoch"] = epoch unless epoch.nil?
+ hash["release"] = release unless release.nil?
+ hash["version"] = version
+ end
+
+ def query(action, parameters)
+ with_helper do
+ json = build_query(action, parameters)
+ Chef::Log.trace "sending '#{json}' to python helper"
+ outpipe.syswrite json + "\n"
+ output = inpipe.sysread(4096).chomp
+ Chef::Log.trace "got '#{output}' from python helper"
+ return output
+ end
+ end
+
+ def build_query(action, parameters)
+ hash = { "action" => action }
+ parameters.each do |param_name, param_value|
+ hash[param_name] = param_value unless param_value.nil?
+ end
+
+ # Special handling for certain action / param combos
+ if %i{whatinstalled whatavailable}.include?(action)
+ add_version(hash, parameters["version"]) unless parameters["version"].nil?
+ end
+
+ FFI_Yajl::Encoder.encode(hash)
+ end
+
+ def parse_response(output)
+ array = output.split.map { |x| x == "nil" ? nil : x }
+ array.each_slice(3).map { |x| Version.new(*x) }.first
+ end
+
+ def drain_fds
+ output = ""
+ fds, = IO.select([stderr, stdout, inpipe], nil, nil, 0)
+ unless fds.nil?
+ fds.each do |fd|
+ output += fd.sysread(4096) rescue ""
+ end
+ end
+ output
+ rescue => e
+ output
+ end
+
+ def with_helper
+ max_retries ||= 5
+ ret = nil
+ Timeout.timeout(600) do
+ check
+ ret = yield
+ end
+ output = drain_fds
+ unless output.empty?
+ Chef::Log.trace "discarding output on stderr/stdout from python helper: #{output}"
+ end
+ ret
+ rescue EOFError, Errno::EPIPE, Timeout::Error, Errno::ESRCH => e
+ output = drain_fds
+ if ( max_retries -= 1 ) > 0
+ unless output.empty?
+ Chef::Log.trace "discarding output on stderr/stdout from python helper: #{output}"
+ end
+ restart
+ retry
+ else
+ raise e if output.empty?
+
+ raise "dnf-helper.py had stderr/stdout output:\n\n#{output}"
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/dnf/version.rb b/lib/chef/provider/package/dnf/version.rb
new file mode 100644
index 0000000000..d6b72a84bd
--- /dev/null
+++ b/lib/chef/provider/package/dnf/version.rb
@@ -0,0 +1,60 @@
+#
+# Copyright:: Copyright (c) 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 Provider
+ class Package
+ class Dnf < Chef::Provider::Package
+
+ # helper class to assist in passing around name/version/arch triples
+ class Version
+ attr_accessor :name
+ attr_accessor :version
+ attr_accessor :arch
+
+ def initialize(name, version, arch)
+ @name = name
+ @version = version
+ @arch = arch
+ end
+
+ def to_s
+ "#{name}-#{version}.#{arch}" unless version.nil?
+ end
+
+ def version_with_arch
+ "#{version}.#{arch}" unless version.nil?
+ end
+
+ def name_with_arch
+ "#{name}.#{arch}" unless name.nil?
+ end
+
+ def matches_name_and_arch?(other)
+ other.version == version && other.arch == arch
+ end
+
+ def ==(other)
+ name == other.name && version == other.version && arch == other.arch
+ end
+
+ alias eql? ==
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/dpkg.rb b/lib/chef/provider/package/dpkg.rb
index e57f58e052..b2d1678caa 100644
--- a/lib/chef/provider/package/dpkg.rb
+++ b/lib/chef/provider/package/dpkg.rb
@@ -16,18 +16,20 @@
# limitations under the License.
#
-require "chef/provider/package"
-require "chef/resource/package"
+require_relative "../package"
+require_relative "deb"
+require_relative "../../resource/package"
class Chef
class Provider
class Package
class Dpkg < Chef::Provider::Package
- DPKG_REMOVED = /^Status: deinstall ok config-files/
- DPKG_INSTALLED = /^Status: install ok installed/
- DPKG_VERSION = /^Version: (.+)$/
+ include Chef::Provider::Package::Deb
+ DPKG_REMOVED = /^Status: deinstall ok config-files/.freeze
+ DPKG_INSTALLED = /^Status: install ok installed/.freeze
+ DPKG_VERSION = /^Version: (.+)$/.freeze
- provides :dpkg_package, os: "linux"
+ provides :dpkg_package
use_multipackage_api
use_package_name_for_source
@@ -73,43 +75,48 @@ class Chef
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)
+ logger.info("#{new_resource} installing package(s): #{name.join(" ")}")
+ run_noninteractive("dpkg", "-i", *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)
+ logger.info("#{new_resource} removing package(s): #{name.join(" ")}")
+ run_noninteractive("dpkg", "-r", *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)
+ logger.info("#{new_resource} purging packages(s): #{name.join(" ")}")
+ run_noninteractive("dpkg", "-P", *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
+ def check_resource_semantics!; end
private
+ # compare 2 versions to each other to see which is newer.
+ # this differs from the standard package method because we
+ # need to be able to parse debian version strings which contain
+ # tildes which Gem cannot properly parse
+ #
+ # @return [Integer] 1 if v1 > v2. 0 if they're equal. -1 if v1 < v2
+ def version_compare(v1, v2)
+ if !shell_out("dpkg", "--compare-versions", v1.to_s, "gt", v2.to_s).error?
+ 1
+ elsif !shell_out("dpkg", "--compare-versions", v1.to_s, "eq", v2.to_s).error?
+ 0
+ else
+ -1
+ end
+ end
+
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])
+ logger.trace("#{new_resource} checking install state of #{package_name}")
+ status = shell_out!("dpkg", "-s", package_name, returns: [0, 1])
package_installed = false
status.stdout.each_line do |line|
case line
@@ -120,12 +127,12 @@ class Chef
package_installed = true
when DPKG_VERSION
if package_installed
- Chef::Log.debug("#{new_resource} current version is #{$1}")
+ logger.trace("#{new_resource} current version is #{$1}")
return $1
end
end
end
- return nil
+ nil
end
def get_current_version_from(array)
@@ -134,12 +141,6 @@ class Chef
end
end
- # 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
-
# Returns true if all sources exist. Returns false if any do not, or if no
# sources were specified.
#
@@ -148,7 +149,7 @@ class Chef
resolved_source_array.all? { |s| s && ::File.exist?(s) }
end
- # Helper to return all the nanes of the missing sources for error messages.
+ # Helper to return all the names of the missing sources for error messages.
#
# @return [Array<String>] Array of missing sources
def missing_sources
@@ -163,10 +164,7 @@ class Chef
#
# @return [Hash] Mapping of package names to sources
def name_sources
- @name_sources =
- begin
- Hash[*package_name_array.zip(resolved_source_array).flatten]
- end
+ @name_sources ||= Hash[*package_name_array.zip(resolved_source_array).flatten]
end
# Helper to construct Hash of names-to-package-information.
@@ -176,8 +174,8 @@ class Chef
@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}")
+ logger.trace("#{new_resource} checking #{src} dpkg status")
+ status = shell_out!("dpkg-deb", "-W", src)
status.stdout
end
Hash[*package_name_array.zip(pkginfos).flatten]
@@ -185,17 +183,11 @@ class Chef
end
def name_candidate_version
- @name_candidate_version ||=
- begin
- Hash[name_pkginfo.map { |k, v| [k, v ? v.split("\t")[1].strip : nil] }]
- end
+ @name_candidate_version ||= name_pkginfo.transform_values { |v| v ? v.split("\t")[1]&.strip : nil }
end
def name_package_name
- @name_package_name ||=
- begin
- Hash[name_pkginfo.map { |k, v| [k, v ? v.split("\t")[0] : nil] }]
- end
+ @name_package_name ||= name_pkginfo.transform_values { |v| v ? v.split("\t")[0] : nil }
end
# Return candidate version array from pkg-deb -W against the source file(s).
@@ -217,7 +209,7 @@ class Chef
#
# @return [Boolean] true if we're doing :install or :upgrade
def installing?
- [:install, :upgrade].include?(action)
+ %i{install upgrade}.include?(action)
end
end
diff --git a/lib/chef/provider/package/easy_install.rb b/lib/chef/provider/package/easy_install.rb
deleted file mode 100644
index 989f2ab9d2..0000000000
--- a/lib/chef/provider/package/easy_install.rb
+++ /dev/null
@@ -1,135 +0,0 @@
-#
-# Author:: Joe Williams (<joe@joetify.com>)
-# Copyright:: Copyright 2009-2016, 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/provider/package"
-require "chef/mixin/command"
-require "chef/resource/package"
-
-class Chef
- class Provider
- class Package
- class EasyInstall < Chef::Provider::Package
-
- provides :easy_install_package
-
- def install_check(name)
- check = false
-
- begin
- # first check to see if we can import it
- 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_with_timeout!("#{python_binary_path} -c \"import sys; print sys.path\"", :returns => [0, 1]).stdout
- if output.downcase.include? "#{name.downcase}"
- check = true
- end
- else
- check = true
- end
- rescue
- # it's probably not installed
- end
-
- check
- end
-
- def easy_install_binary_path
- path = @new_resource.easy_install_binary
- path ? path : "easy_install"
- end
-
- def python_binary_path
- path = @new_resource.python_binary
- path ? path : "python"
- end
-
- def module_name
- m = @new_resource.module_name
- m ? m : @new_resource.name
- end
-
- def load_current_resource
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
-
- # get the currently installed version if installed
- package_version = nil
- if install_check(module_name)
- begin
- 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_with_timeout!("#{python_binary_path} -c \"import sys; print sys.path\"", :returns => [0, 1]).stdout
-
- output_array = output.gsub(/[\[\]]/, "").split(/\s*,\s*/)
- package_path = ""
-
- output_array.each do |entry|
- if entry.downcase.include?(@new_resource.package_name)
- package_path = entry
- end
- end
-
- package_path[/\S\S(.*)\/(.*)-(.*)-py(.*).egg\S/]
- package_version = $3
- end
- end
-
- if package_version == @new_resource.version
- Chef::Log.debug("#{@new_resource} at version #{@new_resource.version}")
- @current_resource.version(@new_resource.version)
- else
- Chef::Log.debug("#{@new_resource} at version #{package_version}")
- @current_resource.version(package_version)
- end
-
- @current_resource
- end
-
- def candidate_version
- return @candidate_version if @candidate_version
-
- # do a dry run to get the latest version
- 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
-
- def install_package(name, version)
- Chef.log_deprecation("The easy_install package provider is deprecated and will be removed in Chef 13.")
- run_command(:command => "#{easy_install_binary_path}#{expand_options(@new_resource.options)} \"#{name}==#{version}\"")
- end
-
- def upgrade_package(name, version)
- install_package(name, version)
- end
-
- def remove_package(name, version)
- Chef.log_deprecation("The easy_install package provider is deprecated and will be removed in Chef 13.")
- run_command(:command => "#{easy_install_binary_path }#{expand_options(@new_resource.options)} -m #{name}")
- end
-
- def purge_package(name, version)
- remove_package(name, version)
- end
-
- end
- end
- end
-end
diff --git a/lib/chef/provider/package/freebsd/base.rb b/lib/chef/provider/package/freebsd/base.rb
index 7104a71f70..46a01e754c 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_relative "../../../resource/package"
+require_relative "../../package"
+require_relative "../../../mixin/get_source_from_package"
class Chef
class Provider
@@ -37,28 +37,31 @@ class Chef
case port
# When the package name starts with a '/' treat it as the full path to the ports directory.
- when /^\//
+ when %r{^/}
port
# Otherwise if the package name contains a '/' not at the start (like 'www/wordpress') treat
# as a relative path from /usr/ports.
- when /\//
+ when %r{/}
"/usr/ports/#{port}"
# Otherwise look up the path to the ports directory using 'whereis'
else
- whereis = shell_out_with_timeout!("whereis -s #{port}", :env => nil)
+ whereis = shell_out!("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
+
path
end
end
def makefile_variable_value(variable, dir = nil)
- options = dir ? { :cwd => dir } : {}
- 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.
+ options = dir ? { cwd: dir } : {}
+ options[:env] = nil
+ options[:returns] = [0, 1]
+ make_v = shell_out!("make", "-V", variable, **options)
+ make_v.exitstatus == 0 ? make_v.stdout.strip.split($OUTPUT_RECORD_SEPARATOR).first : nil
end
end
@@ -67,19 +70,19 @@ class Chef
def initialize(*args)
super
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource = Chef::Resource::Package.new(new_resource.name)
end
def load_current_resource
- @current_resource.package_name(@new_resource.package_name)
+ current_resource.package_name(new_resource.package_name)
- @current_resource.version(current_installed_version)
- Chef::Log.debug("#{@new_resource} current version is #{@current_resource.version}") if @current_resource.version
+ current_resource.version(current_installed_version)
+ logger.trace("#{new_resource} current version is #{current_resource.version}") if current_resource.version
@candidate_version = candidate_version
- Chef::Log.debug("#{@new_resource} candidate version is #{@candidate_version}") if @candidate_version
+ logger.trace("#{new_resource} candidate version is #{@candidate_version}") if @candidate_version
- @current_resource
+ current_resource
end
end
diff --git a/lib/chef/provider/package/freebsd/pkg.rb b/lib/chef/provider/package/freebsd/pkg.rb
deleted file mode 100644
index 78d9449454..0000000000
--- a/lib/chef/provider/package/freebsd/pkg.rb
+++ /dev/null
@@ -1,114 +0,0 @@
-#
-# Authors:: Bryan McLellan (btm@loftninjas.org)
-# Matthew Landauer (matthew@openaustralia.org)
-# Richard Manyanza (liseki@nyikacraftsmen.com)
-# Copyright:: Copyright 2009-2016, Bryan McLellan, Matthew Landauer
-# Copyright:: Copyright 2014-2016, Richard Manyanza
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR 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/freebsd/base"
-require "chef/util/path_helper"
-
-class Chef
- class Provider
- class Package
- module Freebsd
- class Pkg < Base
- include PortsHelper
-
- def install_package(name, version)
- unless @current_resource.version
- case @new_resource.source
- when /^http/, /^ftp/
- if @new_resource.source =~ /\/$/
- shell_out_with_timeout!("pkg_add -r #{package_name}", :env => { "PACKAGESITE" => @new_resource.source, "LC_ALL" => nil }).status
- else
- 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_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_with_timeout!("pkg_add -r #{latest_link_name}", :env => nil).status
- end
- end
- end
-
- def remove_package(name, version)
- 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.
- def package_name
- if supports_ports?
- if makefile_variable_value("PKGNAME", port_path) =~ /^(.+)-[^-]+$/
- $1
- else
- raise Chef::Exceptions::Package, "Unexpected form for PKGNAME variable in #{port_path}/Makefile"
- end
- else
- @new_resource.package_name
- end
- end
-
- def latest_link_name
- makefile_variable_value("LATEST_LINK", port_path)
- end
-
- def current_installed_version
- 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
-
- def candidate_version
- case @new_resource.source
- when /^http/, /^ftp/
- repo_candidate_version
- when /^\//
- file_candidate_version
- else
- ports_candidate_version
- end
- end
-
- def file_candidate_version_path
- Dir[Chef::Util::PathHelper.escape_glob_dir("#{@new_resource.source}/#{@current_resource.package_name}") + "*"][-1].to_s
- end
-
- def file_candidate_version
- file_candidate_version_path.split(/-/).last.split(/.tbz/).first
- end
-
- def repo_candidate_version
- "0.0.0"
- end
-
- def ports_candidate_version
- makefile_variable_value("PORTVERSION", port_path)
- end
-
- def port_path
- port_dir @new_resource.package_name
- end
-
- end
- end
- end
- end
-end
diff --git a/lib/chef/provider/package/freebsd/pkgng.rb b/lib/chef/provider/package/freebsd/pkgng.rb
index 8a805140ce..077464b5c2 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_relative "base"
class Chef
class Provider
@@ -25,46 +25,46 @@ class Chef
class Pkgng < Base
def install_package(name, version)
- unless @current_resource.version
- case @new_resource.source
- when /^(http|ftp|\/)/
- 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}")
-
+ unless current_resource.version
+ case new_resource.source
+ when %r{^(http|ftp|/)}
+ shell_out!("pkg", "add", options, new_resource.source, env: { "LC_ALL" => nil }).status
+ logger.trace("#{new_resource} installed from: #{new_resource.source}")
else
- shell_out_with_timeout!("pkg install -y#{expand_options(@new_resource.options)} #{name}", :env => { "LC_ALL" => nil }).status
+ shell_out!("pkg", "install", "-y", 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 && !options.empty? || options = nil
- shell_out_with_timeout!("pkg delete -y#{expand_options(options)} #{name}#{version ? '-' + version : ''}", :env => nil).status
+ options_dup = options && options.map { |str| str.sub(repo_regex, "") }.reject!(&:empty?)
+ shell_out!("pkg", "delete", "-y", options_dup, "#{name}#{version ? "-" + version : ""}", env: nil).status
end
def current_installed_version
- pkg_info = shell_out_with_timeout!("pkg info \"#{@new_resource.package_name}\"", :env => nil, :returns => [0, 1])
+ # pkgng up to version 1.15.99.7 returns 70 for pkg not found,
+ # later versions return 1
+ pkg_info = shell_out!("pkg", "info", new_resource.package_name, env: nil, returns: [0, 1, 70])
pkg_info.stdout[/^Version +: (.+)$/, 1]
end
def candidate_version
- @new_resource.source ? file_candidate_version : repo_candidate_version
+ new_resource.source ? file_candidate_version : repo_candidate_version
end
private
def file_candidate_version
- @new_resource.source[/#{Regexp.escape(@new_resource.package_name)}-(.+)\.txz/, 1]
+ new_resource.source[/#{Regexp.escape(new_resource.package_name)}-(.+)\.txz/, 1]
end
def repo_candidate_version
- if @new_resource.options && @new_resource.options.match(repo_regex)
- options = $1
+ if options && options.join(" ").match(repo_regex)
+ options = $1.split(" ")
end
- 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
+ pkg_query = shell_out!("pkg", "rquery", options, "%v", new_resource.package_name, env: nil)
+ pkg_query.exitstatus == 0 ? pkg_query.stdout.strip.split('\n').last : nil
end
def repo_regex
diff --git a/lib/chef/provider/package/freebsd/port.rb b/lib/chef/provider/package/freebsd/port.rb
index 3eb3c5ab01..fabc736800 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_relative "base"
class Chef
class Provider
@@ -26,20 +26,16 @@ class Chef
include PortsHelper
def install_package(name, version)
- shell_out_with_timeout!("make -DBATCH install clean", :timeout => 1800, :env => nil, :cwd => port_dir).status
+ shell_out!("make", "-DBATCH", "install", "clean", timeout: 1800, env: nil, cwd: port_dir).status
end
def remove_package(name, version)
- shell_out_with_timeout!("make deinstall", :timeout => 300, :env => nil, :cwd => port_dir).status
+ shell_out!("make", "deinstall", timeout: 300, env: nil, cwd: port_dir).status
end
def current_installed_version
- pkg_info = if @new_resource.supports_pkgng?
- shell_out_with_timeout!("pkg info \"#{@new_resource.package_name}\"", :env => nil, :returns => [0, 70])
- else
- 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]
+ pkg_info = shell_out!("pkg", "info", new_resource.package_name, env: nil, returns: [0, 70])
+ pkg_info.stdout[/^#{Regexp.escape(new_resource.package_name)}-(.+)/, 1]
end
def candidate_version
@@ -51,7 +47,7 @@ class Chef
end
def port_dir
- super(@new_resource.package_name)
+ super(new_resource.package_name)
end
end
end
diff --git a/lib/chef/provider/package/homebrew.rb b/lib/chef/provider/package/homebrew.rb
index a105f6d7d0..2b60c0a1ec 100644
--- a/lib/chef/provider/package/homebrew.rb
+++ b/lib/chef/provider/package/homebrew.rb
@@ -2,8 +2,7 @@
# Author:: Joshua Timberman (<joshua@chef.io>)
# Author:: Graeme Mathieson (<mathie@woss.name>)
#
-# Copyright 2011-2016, Chef Software Inc.
-# Copyright 2014-2016, Chef Software, Inc <legal@chef.io>
+# Copyright:: Copyright (c) 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.
@@ -18,62 +17,67 @@
# limitations under the License.
#
-require "etc"
-require "chef/mixin/homebrew_user"
+require "etc" unless defined?(Etc)
+require_relative "../../mixin/homebrew_user"
class Chef
class Provider
class Package
class Homebrew < Chef::Provider::Package
+ allow_nils
+ use_multipackage_api
- provides :package, os: "darwin", override: true
+ provides :package, os: "darwin"
provides :homebrew_package
include Chef::Mixin::HomebrewUser
def load_current_resource
- self.current_resource = Chef::Resource::Package.new(new_resource.name)
+ @current_resource = Chef::Resource::HomebrewPackage.new(new_resource.name)
current_resource.package_name(new_resource.package_name)
- current_resource.version(current_installed_version)
- Chef::Log.debug("#{new_resource} current version is #{current_resource.version}") if current_resource.version
-
- @candidate_version = candidate_version
-
- Chef::Log.debug("#{new_resource} candidate version is #{@candidate_version}") if @candidate_version
+ current_resource.version(get_current_versions)
+ logger.trace("#{new_resource} current package version(s): #{current_resource.version}") if current_resource.version
current_resource
end
- def install_package(name, version)
- unless current_resource.version == version
- brew("install", new_resource.options, name)
+ def candidate_version
+ package_name_array.map do |package_name|
+ available_version(package_name)
end
end
- def upgrade_package(name, version)
- current_version = current_resource.version
-
- if current_version.nil? || current_version.empty?
- install_package(name, version)
- elsif current_version != version
- brew("upgrade", new_resource.options, name)
+ def get_current_versions
+ package_name_array.map do |package_name|
+ installed_version(package_name)
end
end
- def remove_package(name, version)
- if current_resource.version
- brew("uninstall", new_resource.options, name)
- end
+ def install_package(names, versions)
+ brew_cmd_output("install", options, names.compact)
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
- remove_package(name, version)
+ # upgrades are a bit harder in homebrew than other package formats. If you try to
+ # brew upgrade a package that isn't installed it will fail so if a user specifies
+ # the action of upgrade we need to figure out which packages need to be installed
+ # and which packages can be upgrades. We do this by checking if brew_info has an entry
+ # via the installed_version helper.
+ def upgrade_package(names, versions)
+ # @todo when we no longer support Ruby 2.6 this can be simplified to be a .filter_map
+ upgrade_pkgs = names.select { |x| x if installed_version(x) }.compact
+ install_pkgs = names.select { |x| x unless installed_version(x) }.compact
+
+ brew_cmd_output("upgrade", options, upgrade_pkgs) unless upgrade_pkgs.empty?
+ brew_cmd_output("install", options, install_pkgs) unless install_pkgs.empty?
+ end
+
+ def remove_package(names, versions)
+ brew_cmd_output("uninstall", options, names.compact)
end
- def brew(*args)
- get_response_from_command("brew #{args.join(' ')}")
+ # Homebrew doesn't really have a notion of purging, do a "force remove"
+ def purge_package(names, versions)
+ brew_cmd_output("uninstall", "--force", options, names.compact)
end
# We implement a querying method that returns the JSON-as-Hash
@@ -83,9 +87,50 @@ class Chef
# information, but that is not any more robust than using the
# command-line interface that returns the same thing.
#
- # https://github.com/Homebrew/homebrew/wiki/Querying-Brew
+ # https://docs.brew.sh/Querying-Brew
+ #
+ # @returns [Hash] a hash of package information where the key is the package name
def brew_info
- @brew_info ||= Chef::JSONCompat.from_json(brew("info", "--json=v1", new_resource.package_name)).first
+ @brew_info ||= begin
+ command_array = ["info", "--json=v1"].concat package_name_array
+ # convert the array of hashes into a hash where the key is the package name
+
+ cmd_output = brew_cmd_output(command_array, allow_failure: true)
+
+ if cmd_output.empty?
+ # we had some kind of failure so we need to iterate through each package to find them
+ package_name_array.each_with_object({}) do |package_name, hsh|
+ cmd_output = brew_cmd_output("info", "--json=v1", package_name, allow_failure: true)
+ if cmd_output.empty?
+ hsh[package_name] = {}
+ else
+ json = Chef::JSONCompat.from_json(cmd_output).first
+ hsh[json["name"]] = json
+ end
+ end
+ else
+ Hash[Chef::JSONCompat.from_json(cmd_output).collect { |pkg| [pkg["name"], pkg] }]
+ end
+ end
+ end
+
+ #
+ # Return the package information given a package name or package alias
+ #
+ # @param [String] name_or_alias The name of the package or its alias
+ #
+ # @return [Hash] Package information
+ #
+ def package_info(package_name)
+ # return the package hash if it's in the brew info hash
+ return brew_info[package_name] if brew_info[package_name]
+
+ # check each item in the hash to see if we were passed an alias
+ brew_info.each_value do |p|
+ return p if p["full_name"] == package_name || p["aliases"].include?(package_name)
+ end
+
+ {}
end
# Some packages (formula) are "keg only" and aren't linked,
@@ -94,15 +139,20 @@ class Chef
# "current" (as in latest). Otherwise, we will use the version
# that brew thinks is linked as the current version.
#
- def current_installed_version
- if brew_info["keg_only"]
- if brew_info["installed"].empty?
+ # @param [String] package name
+ #
+ # @returns [String] package version
+ def installed_version(i)
+ p_data = package_info(i)
+
+ if p_data["keg_only"]
+ if p_data["installed"].empty?
nil
else
- brew_info["installed"].last["version"]
+ p_data["installed"].last["version"]
end
else
- brew_info["linked_keg"]
+ p_data["linked_keg"]
end
end
@@ -115,19 +165,33 @@ class Chef
# forward project.
#
# https://github.com/Homebrew/homebrew/wiki/Acceptable-Formulae#stable-versions
- def candidate_version
- brew_info["versions"]["stable"]
- end
+ #
+ # @param [String] package name
+ #
+ # @returns [String] package version
+ def available_version(i)
+ p_data = package_info(i)
- private
+ # nothing is available
+ return nil if p_data.empty?
- def get_response_from_command(command)
+ p_data["versions"]["stable"]
+ end
+
+ def brew_cmd_output(*command, **options)
homebrew_uid = find_homebrew_uid(new_resource.respond_to?(:homebrew_user) && new_resource.homebrew_user)
homebrew_user = Etc.getpwuid(homebrew_uid)
- Chef::Log.debug "Executing '#{command}' as user '#{homebrew_user.name}'"
+ logger.trace "Executing 'brew #{command.join(" ")}' as user '#{homebrew_user.name}'"
+
+ # allow the calling method to decide if the cmd should raise or not
+ # brew_info uses this when querying out available package info since a bad
+ # package name will raise and we want to surface a nil available package so that
+ # the package provider can magically handle that
+ shell_out_cmd = options[:allow_failure] ? :shell_out : :shell_out!
+
# 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, "TMPDIR" => nil })
+ output = send(shell_out_cmd, "brew", *command, timeout: 1800, user: homebrew_uid, environment: { "HOME" => homebrew_user.dir, "RUBYOPT" => nil, "TMPDIR" => nil })
output.stdout.chomp
end
diff --git a/lib/chef/provider/package/ips.rb b/lib/chef/provider/package/ips.rb
index 85053d47f2..ee997d147f 100644
--- a/lib/chef/provider/package/ips.rb
+++ b/lib/chef/provider/package/ips.rb
@@ -1,7 +1,7 @@
#
# Author:: Jason J. W. Williams (<williamsjj@digitar.com>)
# Author:: Stephen Nelson-Smith (<sns@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,17 +18,16 @@
#
require "open3"
-require "chef/provider/package"
-require "chef/mixin/command"
-require "chef/resource/package"
+require_relative "../package"
+require_relative "../../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"
+ provides :package, platform: %w{openindiana omnios solaris2}
+ provides :ips_package
attr_accessor :virtual
@@ -36,45 +35,40 @@ class Chef
super
requirements.assert(:all_actions) do |a|
- a.assertion { ! @candidate_version.nil? }
- a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.package_name} not found"
- a.whyrun "Assuming package #{@new_resource.package_name} would have been made available."
+ a.assertion { !@candidate_version.nil? }
+ a.failure_message Chef::Exceptions::Package, "Package #{new_resource.package_name} not found"
+ a.whyrun "Assuming package #{new_resource.package_name} would have been made available."
end
end
def get_current_version
- shell_out_with_timeout("pkg info #{@new_resource.package_name}").stdout.each_line do |line|
+ shell_out("pkg", "info", new_resource.package_name).stdout.each_line do |line|
return $1.split[0] if line =~ /^\s+Version: (.*)/
end
- return nil
+ nil
end
def get_candidate_version
- shell_out_with_timeout!("pkg info -r #{new_resource.package_name}").stdout.each_line do |line|
+ shell_out!("pkg", "info", "-r", new_resource.package_name).stdout.each_line do |line|
return $1.split[0] if line =~ /Version: (.*)/
end
- return nil
+ nil
end
def load_current_resource
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
- Chef::Log.debug("Checking package status for #{@new_resource.name}")
- @current_resource.version(get_current_version)
+ @current_resource = Chef::Resource::IpsPackage.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
+ logger.trace("Checking package status for #{new_resource.package_name}")
+ current_resource.version(get_current_version)
@candidate_version = get_candidate_version
- @current_resource
+ current_resource
end
def install_package(name, version)
- package_name = "#{name}@#{version}"
- normal_command = "pkg#{expand_options(@new_resource.options)} install -q #{package_name}"
- command =
- if @new_resource.respond_to?(:accept_license) && @new_resource.accept_license
- normal_command.gsub("-q", "-q --accept")
- else
- normal_command
- end
- shell_out_with_timeout(command)
+ command = [ "pkg", options, "install", "-q" ]
+ command << "--accept" if new_resource.accept_license
+ command << "#{name}@#{version}"
+ shell_out!(command)
end
def upgrade_package(name, version)
@@ -83,7 +77,7 @@ class Chef
def remove_package(name, version)
package_name = "#{name}@#{version}"
- shell_out_with_timeout!( "pkg#{expand_options(@new_resource.options)} uninstall -q #{package_name}" )
+ shell_out!( "pkg", 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 7bbc68aba8..a653dbd418 100644
--- a/lib/chef/provider/package/macports.rb
+++ b/lib/chef/provider/package/macports.rb
@@ -2,30 +2,28 @@ class Chef
class Provider
class Package
class Macports < Chef::Provider::Package
-
- provides :package, os: "darwin"
provides :macports_package
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::Package.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
- @current_resource.version(current_installed_version)
- Chef::Log.debug("#{@new_resource} current version is #{@current_resource.version}") if @current_resource.version
+ current_resource.version(current_installed_version)
+ logger.trace("#{new_resource} current version is #{current_resource.version}") if current_resource.version
@candidate_version = macports_candidate_version
- if !@new_resource.version && !@candidate_version
- raise Chef::Exceptions::Package, "Could not get a candidate version for this package -- #{@new_resource.name} does not seem to be a valid package!"
+ if !new_resource.version && !@candidate_version
+ raise Chef::Exceptions::Package, "Could not get a candidate version for this package -- #{new_resource.package_name} does not seem to be a valid package!"
end
- Chef::Log.debug("#{@new_resource} candidate version is #{@candidate_version}") if @candidate_version
+ logger.trace("#{new_resource} candidate version is #{@candidate_version}") if @candidate_version
- @current_resource
+ current_resource
end
def current_installed_version
- command = "port installed #{@new_resource.package_name}"
+ command = [ "port", "installed", new_resource.package_name ]
output = get_response_from_command(command)
response = nil
@@ -37,7 +35,7 @@ class Chef
end
def macports_candidate_version
- command = "port info --version #{@new_resource.package_name}"
+ command = [ "port", "info", "--version", new_resource.package_name ]
output = get_response_from_command(command)
match = output.match(/^version: (.+)$/)
@@ -46,37 +44,37 @@ class Chef
end
def install_package(name, version)
- unless @current_resource.version == version
- command = "port#{expand_options(@new_resource.options)} install #{name}"
- command << " @#{version}" if version && !version.empty?
- shell_out_with_timeout!(command)
+ unless current_resource.version == version
+ command = [ "port", options, "install", name ]
+ command << "@#{version}" if version && !version.empty?
+ shell_out!(command)
end
end
def purge_package(name, version)
- command = "port#{expand_options(@new_resource.options)} uninstall #{name}"
- command << " @#{version}" if version && !version.empty?
- shell_out_with_timeout!(command)
+ command = [ "port", options, "uninstall", name ]
+ command << "@#{version}" if version && !version.empty?
+ shell_out!(command)
end
def remove_package(name, version)
- command = "port#{expand_options(@new_resource.options)} deactivate #{name}"
- command << " @#{version}" if version && !version.empty?
+ command = [ "port", options, "deactivate", name ]
+ command << "@#{version}" if version && !version.empty?
- shell_out_with_timeout!(command)
+ shell_out!(command)
end
def upgrade_package(name, version)
# Saving this to a variable -- weird rSpec behavior
# happens otherwise...
- current_version = @current_resource.version
+ current_version = current_resource.version
if current_version.nil? || current_version.empty?
# Macports doesn't like when you upgrade a package
# that hasn't been installed.
install_package(name, version)
elsif current_version != version
- shell_out_with_timeout!( "port#{expand_options(@new_resource.options)} upgrade #{name} @#{version}" )
+ shell_out!( "port", options, "upgrade", name, "@#{version}" )
end
end
@@ -84,15 +82,16 @@ class Chef
def get_response_from_command(command)
output = nil
- status = shell_out_with_timeout(command)
+ status = shell_out(command)
begin
output = status.stdout
rescue Exception
raise Chef::Exceptions::Package, "Could not read from STDOUT on command: #{command}"
end
unless status.exitstatus == 0 || status.exitstatus == 1
- raise Chef::Exceptions::Package, "#{command} failed - #{status.insect}!"
+ raise Chef::Exceptions::Package, "#{command} failed - #{status.inspect}!"
end
+
output
end
end
diff --git a/lib/chef/provider/package/msu.rb b/lib/chef/provider/package/msu.rb
new file mode 100644
index 0000000000..4208260cbe
--- /dev/null
+++ b/lib/chef/provider/package/msu.rb
@@ -0,0 +1,166 @@
+#
+# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>)
+# Copyright:: Copyright (c) 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.
+#
+
+# msu_package leverages cab_package
+# The contents of msu file are extracted, which contains one or more cab files.
+# The extracted cab files are installed using Chef::Resource::Package::CabPackage
+# Reference: https://support.microsoft.com/en-in/kb/934307
+require_relative "../package"
+require_relative "../../resource/msu_package"
+require_relative "../../mixin/shell_out"
+require_relative "cab"
+require_relative "../../util/path_helper"
+require_relative "../../mixin/uris"
+require_relative "../../mixin/checksum"
+require "cgi" unless defined?(CGI)
+
+class Chef
+ class Provider
+ class Package
+ class Msu < Chef::Provider::Package
+ include Chef::Mixin::ShellOut
+ include Chef::Mixin::Uris
+ include Chef::Mixin::Checksum
+
+ provides :msu_package
+
+ def load_current_resource
+ @current_resource = Chef::Resource::MsuPackage.new(new_resource.name)
+
+ # download file if source is a url
+ msu_file = uri_scheme?(new_resource.source) ? download_source_file : new_resource.source
+
+ # temp directory where the contents of msu file get extracted
+ @temp_directory = Dir.mktmpdir("chef")
+ extract_msu_contents(msu_file, @temp_directory)
+ @cab_files = read_cab_files_from_xml(@temp_directory)
+
+ if @cab_files.empty?
+ raise Chef::Exceptions::Package, "Corrupt MSU package: MSU package XML does not contain any cab file"
+ else
+ current_resource.version(get_current_versions)
+ end
+
+ current_resource
+ end
+
+ def get_current_versions
+ @cab_files.map do |cabfile|
+ cab_pkg = get_cab_package(cabfile)
+ cab_pkg.installed_version
+ end
+ end
+
+ def get_candidate_versions
+ @cab_files.map do |cabfile|
+ cab_pkg = get_cab_package(cabfile)
+ cab_pkg.package_version
+ end
+ end
+
+ def candidate_version
+ @candidate_version ||= get_candidate_versions
+ end
+
+ def get_cab_package(cab_file)
+ cab_resource = new_resource
+ cab_resource.source = cab_file
+ Chef::Provider::Package::Cab.new(cab_resource, nil)
+ end
+
+ def download_source_file
+ source_resource.run_action(:create)
+ logger.trace("#{new_resource} fetched source file to #{source_resource.path}")
+ source_resource.path
+ end
+
+ def source_resource
+ @source_resource ||= declare_resource(:remote_file, new_resource.name) do
+ path default_download_cache_path
+ source new_resource.source
+ checksum new_resource.checksum
+ backup false
+ end
+ end
+
+ def default_download_cache_path
+ uri = ::URI.parse(new_resource.source)
+ filename = ::File.basename(::CGI.unescape(uri.path))
+ file_cache_dir = Chef::FileCache.create_cache_path("package/")
+ Chef::Util::PathHelper.cleanpath("#{file_cache_dir}/#{filename}")
+ end
+
+ def install_package(name, version)
+ # use cab_package resource to install the extracted cab packages
+ @cab_files.each do |cab_file|
+ declare_resource(:cab_package, new_resource.name) do
+ source cab_file
+ timeout new_resource.timeout
+ action :install
+ end
+ end
+ end
+
+ def remove_package(name, version)
+ # use cab_package provider to remove the extracted cab packages
+ @cab_files.each do |cab_file|
+ declare_resource(:cab_package, new_resource.name) do
+ source cab_file
+ timeout new_resource.timeout
+ action :remove
+ end
+ end
+ end
+
+ def extract_msu_contents(msu_file, destination)
+ with_os_architecture(nil) do
+ shell_out!("#{ENV["SYSTEMROOT"]}\\system32\\expand.exe -f:* #{msu_file} #{destination}")
+ end
+ end
+
+ # msu package can contain multiple cab files
+ # Reading cab files from xml to ensure the order of installation in case of multiple cab files
+ def read_cab_files_from_xml(msu_dir)
+ # get the file with .xml extension
+ xml_files = Dir.glob("#{msu_dir}/*.xml")
+ cab_files = []
+
+ if xml_files.empty?
+ raise Chef::Exceptions::Package, "Corrupt MSU package: MSU package doesn't contain any xml file"
+ else
+ # msu package contains only single xml file. So using xml_files.first is sufficient
+ doc = ::File.open(xml_files.first.to_s) { |f| REXML::Document.new f }
+ locations = doc.elements.each("unattend/servicing/package/source") { |element| element.attributes["location"] }
+ locations.each do |loc|
+ cab_files << msu_dir + "/" + loc.attribute("location").value.split("\\")[1]
+ end
+
+ cab_files
+ end
+
+ cab_files
+ end
+
+ def cleanup_after_converge
+ # delete the temp directory where the contents of msu file are extracted
+ FileUtils.rm_rf(@temp_directory) if Dir.exist?(@temp_directory)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/openbsd.rb b/lib/chef/provider/package/openbsd.rb
index 8043c01693..6e32a424e2 100644
--- a/lib/chef/provider/package/openbsd.rb
+++ b/lib/chef/provider/package/openbsd.rb
@@ -20,10 +20,10 @@
# limitations under the License.
#
-require "chef/resource/package"
-require "chef/provider/package"
-require "chef/mixin/get_source_from_package"
-require "chef/exceptions"
+require_relative "../../resource/package"
+require_relative "../package"
+require_relative "../../mixin/get_source_from_package"
+require_relative "../../exceptions"
class Chef
class Provider
@@ -42,9 +42,9 @@ class Chef
end
def load_current_resource
- @current_resource.package_name(new_resource.package_name)
- @current_resource.version(installed_version)
- @current_resource
+ current_resource.package_name(new_resource.package_name)
+ current_resource.version(installed_version)
+ current_resource
end
def define_resource_requirements
@@ -53,11 +53,11 @@ 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 property")
end
requirements.assert(:all_actions) do |a|
a.assertion do
- if new_resource.package_name =~ /^(.+?)--(.+)/
+ if /^(.+?)--(.+)/.match?(new_resource.package_name)
!new_resource.version
else
true
@@ -68,12 +68,12 @@ class Chef
end
def install_package(name, version)
- unless @current_resource.version
+ unless current_resource.version
if parts = name.match(/^(.+?)--(.+)/) # use double-dash for stems with flavors, see man page for pkg_add
name = parts[1]
end
- 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")
+ shell_out!("pkg_add", "-r", package_string(name, version), env: { "PKG_PATH" => pkg_path }).status
+ logger.trace("#{new_resource.package_name} installed")
end
end
@@ -81,49 +81,52 @@ class Chef
if parts = name.match(/^(.+?)--(.+)/)
name = parts[1]
end
- shell_out_with_timeout!("pkg_delete #{name}#{version_string(version)}", :env => nil).status
+ shell_out!("pkg_delete", package_string(name, version), env: nil).status
end
private
def installed_version
- if parts = new_resource.package_name.match(/^(.+?)--(.+)/)
- name = parts[1]
- else
- name = new_resource.package_name
- end
- pkg_info = shell_out_with_timeout!("pkg_info -e \"#{name}->0\"", :env => nil, :returns => [0, 1])
+ name = if parts = new_resource.package_name.match(/^(.+?)--(.+)/)
+ parts[1]
+ else
+ new_resource.package_name
+ end
+ pkg_info = shell_out!("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}'")
+ logger.trace("installed_version of '#{new_resource.package_name}' is '#{result}'")
result
end
def candidate_version
@candidate_version ||= begin
results = []
- 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
- results << line[/^#{Regexp.escape(new_resource.package_name)}-(.+?)\s/, 1]
- end
+ shell_out!("pkg_info", "-I", package_string(new_resource.package_name, new_resource.version), env: nil, returns: [0, 1]).stdout.each_line do |line|
+ results << if parts = new_resource.package_name.match(/^(.+?)--(.+)/)
+ line[/^#{Regexp.escape(parts[1])}-(.+?)\s/, 1]
+ else
+ line[/^#{Regexp.escape(new_resource.package_name)}-(.+?)\s/, 1]
+ end
end
results = results.reject(&:nil?)
- Chef::Log.debug("Candidate versions of '#{new_resource.package_name}' are '#{results}'")
+ logger.trace("Candidate versions of '#{new_resource.package_name}' are '#{results}'")
case results.length
when 0
[]
when 1
results[0]
else
- raise Chef::Exceptions::Package, "#{new_resource.name} has multiple matching candidates. Please use a more specific name" if results.length > 1
+ raise Chef::Exceptions::Package, "#{new_resource.package_name} has multiple matching candidates. Please use a more specific name" if results.length > 1
end
end
end
- def version_string(version)
- ver = ""
- ver += "-#{version}" if version
+ def package_string(name, version)
+ if version
+ "#{name}-#{version}"
+ else
+ name
+ end
end
def pkg_path
diff --git a/lib/chef/provider/package/pacman.rb b/lib/chef/provider/package/pacman.rb
index bd8028d881..2de2d12579 100644
--- a/lib/chef/provider/package/pacman.rb
+++ b/lib/chef/provider/package/pacman.rb
@@ -16,9 +16,8 @@
# limitations under the License.
#
-require "chef/provider/package"
-require "chef/mixin/command"
-require "chef/resource/package"
+require_relative "../package"
+require_relative "../../resource/package"
class Chef
class Provider
@@ -26,64 +25,55 @@ class Chef
class Pacman < Chef::Provider::Package
provides :package, platform: "arch"
- provides :pacman_package, os: "linux"
+ provides :pacman_package
- def load_current_resource
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
-
- Chef::Log.debug("#{@new_resource} checking pacman for #{@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?)*: (.+)$/
- Chef::Log.debug("#{@new_resource} current version is #{$2}")
- @current_resource.version($2)
- end
- end
-
- unless status.exitstatus == 0 || status.exitstatus == 1
- raise Chef::Exceptions::Package, "pacman failed - #{status.inspect}!"
- end
+ use_multipackage_api
+ allow_nils
- @current_resource
- end
-
- def candidate_version
- return @candidate_version if @candidate_version
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
+ current_resource.version = []
repos = %w{extra core community}
- if ::File.exists?("/etc/pacman.conf")
+ if ::File.exist?("/etc/pacman.conf")
pacman = ::File.read("/etc/pacman.conf")
repos = pacman.scan(/\[(.+)\]/).flatten
end
- package_repos = repos.map { |r| Regexp.escape(r) }.join("|")
-
- status = shell_out_with_timeout("pacman -Sl")
- status.stdout.each_line do |line|
- case line
- when /^(#{package_repos}) #{Regexp.escape(@new_resource.package_name)} (.+)$/
- # $2 contains a string like "4.4.0-1" or "3.10-4 [installed]"
- # simply split by space and use first token
- @candidate_version = $2.split(" ").first
- end
- end
+ repos = Regexp.union(repos)
+ status = shell_out("pacman", "-Sl")
unless status.exitstatus == 0 || status.exitstatus == 1
raise Chef::Exceptions::Package, "pacman failed - #{status.inspect}!"
end
- unless @candidate_version
- raise Chef::Exceptions::Package, "pacman does not have a version of package #{@new_resource.package_name}"
+ pkg_db_data = status.stdout
+ @candidate_version = []
+ package_name_array.each do |pkg|
+ pkg_data = pkg_db_data.match(/(#{repos}) #{pkg} (?<candidate>.*?-[0-9]+)(?<installed> \[.*?( (?<current>.*?-[0-9]+))?\])?\n/m)
+ unless pkg_data
+ raise Chef::Exceptions::Package, "pacman does not have a version of package #{pkg}"
+ end
+
+ @candidate_version << pkg_data[:candidate]
+ if pkg_data[:installed]
+ current_resource.version << (pkg_data[:current] || pkg_data[:candidate])
+ else
+ current_resource.version << nil
+ end
end
+ current_resource
+ end
+
+ def candidate_version
@candidate_version
end
def install_package(name, version)
- shell_out_with_timeout!( "pacman --sync --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" )
+ shell_out!("pacman", "--sync", "--noconfirm", "--noprogressbar", options, *name)
end
def upgrade_package(name, version)
@@ -91,7 +81,7 @@ class Chef
end
def remove_package(name, version)
- shell_out_with_timeout!( "pacman --remove --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" )
+ shell_out!("pacman", "--remove", "--noconfirm", "--noprogressbar", 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 557e7ebc22..853d0b8cfc 100644
--- a/lib/chef/provider/package/paludis.rb
+++ b/lib/chef/provider/package/paludis.rb
@@ -1,6 +1,6 @@
#
# Author:: Vasiliy Tolstov (<v.tolstov@selfip.ru>)
-# Copyright:: Copyright 2014-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/provider/package"
-require "chef/resource/package"
+require_relative "../package"
+require_relative "../../resource/package"
class Chef
class Provider
@@ -25,41 +25,41 @@ class Chef
class Paludis < Chef::Provider::Package
provides :package, platform: "exherbo"
- provides :paludis_package, os: "linux"
+ provides :paludis_package
def load_current_resource
- @current_resource = Chef::Resource::Package.new(@new_resource.package_name)
- @current_resource.package_name(@new_resource.package_name)
+ @current_resource = Chef::Resource::Package.new(new_resource.package_name)
+ current_resource.package_name(new_resource.package_name)
- Chef::Log.debug("Checking package status for #{@new_resource.package_name}")
+ logger.trace("Checking package status for #{new_resource.package_name}")
installed = false
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|
+ 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"
- next
- when "installed"
- installed = true
- @current_resource.version(res[2])
- else
- @candidate_version = res[2]
- end
+ next if res.nil?
+
+ case res[3]
+ when "accounts", "installed-accounts"
+ next
+ when "installed"
+ installed = true
+ current_resource.version(res[2])
+ else
+ @candidate_version = res[2]
end
end
- @current_resource
+ current_resource
end
def install_package(name, version)
- if version
- pkg = "=#{name}-#{version}"
- else
- pkg = "#{@new_resource.package_name}"
- end
- shell_out!("cave -L warning resolve -x#{expand_options(@new_resource.options)} \"#{pkg}\"", :timeout => @new_resource.timeout)
+ pkg = if version
+ "=#{name}-#{version}"
+ else
+ new_resource.package_name.to_s
+ end
+ shell_out!("cave", "-L", "warning", "resolve", "-x", options, pkg)
end
def upgrade_package(name, version)
@@ -67,13 +67,13 @@ class Chef
end
def remove_package(name, version)
- if version
- pkg = "=#{@new_resource.package_name}-#{version}"
- else
- pkg = "#{@new_resource.package_name}"
- end
+ pkg = if version
+ "=#{new_resource.package_name}-#{version}"
+ else
+ new_resource.package_name.to_s
+ end
- shell_out!("cave -L warning uninstall -x#{expand_options(@new_resource.options)} \"#{pkg}\"")
+ shell_out!("cave", "-L", "warning", "uninstall", "-x", options, pkg)
end
def purge_package(name, version)
diff --git a/lib/chef/provider/package/portage.rb b/lib/chef/provider/package/portage.rb
index 52b46b04b4..9975010e5b 100644
--- a/lib/chef/provider/package/portage.rb
+++ b/lib/chef/provider/package/portage.rb
@@ -1,6 +1,6 @@
#
# Author:: Ezra Zygmuntowicz (<ezra@engineyard.com>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,10 +16,9 @@
# limitations under the License.
#
-require "chef/provider/package"
-require "chef/mixin/command"
-require "chef/resource/package"
-require "chef/util/path_helper"
+require_relative "../package"
+require_relative "../../resource/portage_package"
+require_relative "../../util/path_helper"
class Chef
class Provider
@@ -29,13 +28,13 @@ class Chef
provides :package, platform: "gentoo"
provides :portage_package
- PACKAGE_NAME_PATTERN = %r{(?:([^/]+)/)?([^/]+)}
+ PACKAGE_NAME_PATTERN = %r{^(?:([^/]+)/)?([^/]+)$}.freeze
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::PortagePackage.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
- category, pkg = %r{^#{PACKAGE_NAME_PATTERN}$}.match(@new_resource.package_name)[1, 2]
+ category, pkg = PACKAGE_NAME_PATTERN.match(new_resource.package_name)[1, 2]
globsafe_category = category ? Chef::Util::PathHelper.escape_glob_dir(category) : nil
globsafe_pkg = Chef::Util::PathHelper.escape_glob_dir(pkg)
@@ -47,59 +46,54 @@ class Chef
end.compact
if versions.size > 1
- atoms = versions.map { |v| v.first }.sort
+ atoms = versions.map(&:first).sort
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."
+ raise Chef::Exceptions::Package, "Multiple packages found for #{new_resource.package_name}: #{atoms.join(" ")}. Specify a category."
end
elsif versions.size == 1
- @current_resource.version(versions.first.last)
- Chef::Log.debug("#{@new_resource} current version #{$1}")
+ current_resource.version(versions.first.last)
+ logger.trace("#{new_resource} current version #{$1}")
end
- @current_resource
+ current_resource
end
- def parse_emerge(package, txt)
- availables = {}
- found_package_name = nil
+ def raise_error_for_query(msg)
+ raise Chef::Exceptions::Package, "Query for '#{new_resource.package_name}' #{msg}"
+ end
- txt.each_line do |line|
- if line =~ /\*\s+#{PACKAGE_NAME_PATTERN}/
- found_package_name = $&.delete("*").strip
- if package =~ /\// #the category is specified
- if found_package_name == package
- availables[found_package_name] = nil
- end
- else #the category is not specified
- if found_package_name.split("/").last == package
- availables[found_package_name] = nil
- end
+ def candidate_version
+ return @candidate_version if @candidate_version
+
+ pkginfo = shell_out("portageq", "best_visible", "/", new_resource.package_name)
+
+ if pkginfo.exitstatus != 0
+ pkginfo.stderr.each_line do |line|
+ # cspell:disable-next-line
+ if /[Uu]nqualified atom .*match.* multiple/.match?(line)
+ raise_error_for_query("matched multiple packages (please specify a category):\n#{pkginfo.inspect}")
end
end
- if line =~ /Latest version available: (.*)/ && availables.has_key?(found_package_name)
- availables[found_package_name] = $1.strip
+ if pkginfo.stdout.strip.empty?
+ raise_error_for_query("did not find a matching package:\n#{pkginfo.inspect}")
end
- end
- if availables.size > 1
- # shouldn't happen if a category is specified so just use `package`
- raise Chef::Exceptions::Package, "Multiple emerge results found for #{package}: #{availables.keys.join(" ")}. Specify a category."
+ raise_error_for_query("resulted in an unknown error:\n#{pkginfo.inspect}")
end
- availables.values.first
- end
-
- def candidate_version
- return @candidate_version if @candidate_version
-
- status = shell_out("emerge --color n --nospinner --search #{@new_resource.package_name.split('/').last}")
- available, installed = parse_emerge(@new_resource.package_name, status.stdout)
- @candidate_version = available
+ if pkginfo.stdout.lines.count > 1
+ raise_error_for_query("produced unexpected output (multiple lines):\n#{pkginfo.inspect}")
+ end
- unless status.exitstatus == 0
- raise Chef::Exceptions::Package, "emerge --search failed - #{status.inspect}!"
+ pkginfo.stdout.chomp!
+ if /-r\d+$/.match?(pkginfo.stdout)
+ # Latest/Best version of the package is a revision (-rX).
+ @candidate_version = pkginfo.stdout.split(/(?<=-)/).last(2).join
+ else
+ # Latest/Best version of the package is NOT a revision (-rX).
+ @candidate_version = pkginfo.stdout.split("-").last
end
@candidate_version
@@ -113,7 +107,7 @@ class Chef
pkg = "~#{name}-#{$1}"
end
- shell_out!( "emerge -g --color n --nospinner --quiet#{expand_options(@new_resource.options)} #{pkg}" )
+ shell_out!( "emerge", "-g", "--color", "n", "--nospinner", "--quiet", options, pkg )
end
def upgrade_package(name, version)
@@ -121,13 +115,13 @@ class Chef
end
def remove_package(name, version)
- if version
- pkg = "=#{@new_resource.package_name}-#{version}"
- else
- pkg = "#{@new_resource.package_name}"
- end
+ pkg = if version
+ "=#{new_resource.package_name}-#{version}"
+ else
+ new_resource.package_name.to_s
+ end
- shell_out!( "emerge --unmerge --color n --nospinner --quiet#{expand_options(@new_resource.options)} #{pkg}" )
+ shell_out!( "emerge", "--unmerge", "--color", "n", "--nospinner", "--quiet", options, pkg )
end
def purge_package(name, version)
diff --git a/lib/chef/provider/package/powershell.rb b/lib/chef/provider/package/powershell.rb
new file mode 100644
index 0000000000..1f59360c61
--- /dev/null
+++ b/lib/chef/provider/package/powershell.rb
@@ -0,0 +1,137 @@
+# Author:: Dheeraj Dubey(dheeraj.dubey@msystechnologies.com)
+# Copyright:: Copyright (c) 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_relative "../package"
+require_relative "../../resource/powershell_package"
+require_relative "../../mixin/powershell_out"
+
+class Chef
+ class Provider
+ class Package
+ class Powershell < Chef::Provider::Package
+ include Chef::Mixin::PowershellOut
+
+ provides :powershell_package
+
+ def load_current_resource
+ @current_resource = Chef::Resource::PowershellPackage.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
+ if powershell_version < 5
+ raise "Minimum installed PowerShell Version required is 5"
+ end
+
+ requirements.assert(: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
+
+ def candidate_version
+ @candidate_version ||= build_candidate_versions
+ end
+
+ # Installs the package specified with the version passed else latest version will be installed
+ def install_package(names, versions)
+ names.each_with_index do |name, index|
+ cmd = powershell_out(build_powershell_package_command("Install-Package '#{name}'", versions[index]), timeout: new_resource.timeout)
+ next if cmd.nil?
+ raise Chef::Exceptions::PowershellCmdletException, "Failed to install package due to catalog signing error, use skip_publisher_check to force install" if /SkipPublisherCheck/.match?(cmd.stderr)
+ end
+ end
+
+ # Removes the package for the version passed and if no version is passed, then all installed versions of the package are removed
+ def remove_package(names, versions)
+ names.each_with_index do |name, index|
+ if versions && !versions[index].nil?
+ powershell_out(build_powershell_package_command("Uninstall-Package '#{name}'", versions[index]), timeout: new_resource.timeout)
+ else
+ version = "0"
+ until version.empty?
+ version = powershell_out(build_powershell_package_command("Uninstall-Package '#{name}'"), timeout: new_resource.timeout).stdout.strip
+ unless version.empty?
+ logger.info("Removed package '#{name}' with version #{version}")
+ end
+ end
+ end
+ end
+ end
+
+ # Returns array of available available online
+ def build_candidate_versions
+ versions = []
+ new_resource.package_name.each_with_index do |name, index|
+ version = if new_resource.version && !new_resource.version[index].nil?
+ powershell_out(build_powershell_package_command("Find-Package '#{name}'", new_resource.version[index]), timeout: new_resource.timeout).stdout.strip
+ else
+ powershell_out(build_powershell_package_command("Find-Package '#{name}'"), timeout: new_resource.timeout).stdout.strip
+ end
+ if version.empty?
+ version = nil
+ end
+ versions.push(version)
+ end
+ versions
+ end
+
+ # Returns version array of installed version on the system
+ def build_current_versions
+ version_list = []
+ new_resource.package_name.each_with_index do |name, index|
+ version = if new_resource.version && !new_resource.version[index].nil?
+ powershell_out(build_powershell_package_command("Get-Package '#{name}'", new_resource.version[index]), timeout: new_resource.timeout).stdout.strip
+ else
+ powershell_out(build_powershell_package_command("Get-Package '#{name}'"), timeout: new_resource.timeout).stdout.strip
+ end
+ if version.empty?
+ version = nil
+ end
+ version_list.push(version)
+ end
+ version_list
+ end
+
+ def build_powershell_package_command(command, version = nil)
+ command = [command] unless command.is_a?(Array)
+ cmdlet_name = command.first
+ command.unshift("(")
+ # PowerShell Gallery requires tls 1.2
+ command.unshift("if ([Net.ServicePointManager]::SecurityProtocol -lt [Net.SecurityProtocolType]::Tls12) { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 };")
+ # -WarningAction SilentlyContinue is used to suppress the warnings from stdout
+ %w{-Force -ForceBootstrap -WarningAction SilentlyContinue}.each do |arg|
+ command.push(arg)
+ end
+ command.push("-RequiredVersion #{version}") if version
+ command.push("-Source #{new_resource.source}") if new_resource.source && cmdlet_name =~ Regexp.union(/Install-Package/, /Find-Package/)
+ command.push("-SkipPublisherCheck") if new_resource.skip_publisher_check && cmdlet_name !~ /Find-Package/
+ command.push(").Version")
+ command.join(" ")
+ end
+
+ def check_resource_semantics!
+ # This validation method from Chef::Provider::Package does not apply here, so no-op it.
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/rpm.rb b/lib/chef/provider/package/rpm.rb
index 777cc6d209..dafb0b9949 100644
--- a/lib/chef/provider/package/rpm.rb
+++ b/lib/chef/provider/package/rpm.rb
@@ -1,6 +1,6 @@
#
# Author:: Joshua Timberman (<joshua@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,17 +15,16 @@
# 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_relative "../package"
+require_relative "../../resource/package"
+require_relative "../../mixin/get_source_from_package"
+require_relative "yum/rpm_utils"
class Chef
class Provider
class Package
class Rpm < Chef::Provider::Package
-
- provides :rpm_package, os: %w{linux aix}
+ provides :rpm_package
include Chef::Mixin::GetSourceFromPackage
@@ -34,13 +33,13 @@ class Chef
requirements.assert(:all_actions) do |a|
a.assertion { @package_source_exists }
- a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
- a.whyrun "Assuming package #{@new_resource.name} would have been made available."
+ a.failure_message Chef::Exceptions::Package, "Package #{new_resource.package_name} not found: #{new_resource.source}"
+ a.whyrun "Assuming package #{new_resource.package_name} would have been made available."
end
requirements.assert(:all_actions) do |a|
a.assertion { !@rpm_status.nil? && (@rpm_status.exitstatus == 0 || @rpm_status.exitstatus == 1) }
a.failure_message Chef::Exceptions::Package, "Unable to determine current version due to RPM failure. Detail: #{@rpm_status.inspect}"
- a.whyrun "Assuming current version would have been determined for package#{@new_resource.name}."
+ a.whyrun "Assuming current version would have been determined for package #{new_resource.package_name}."
end
end
@@ -48,74 +47,79 @@ class Chef
@package_source_provided = true
@package_source_exists = true
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
+ @current_resource = Chef::Resource::Package.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
- if @new_resource.source
- unless uri_scheme?(@new_resource.source) || ::File.exists?(@new_resource.source)
+ if new_resource.source
+ unless uri_scheme?(new_resource.source) || ::File.exist?(new_resource.source)
@package_source_exists = false
return
end
- Chef::Log.debug("#{@new_resource} checking rpm status")
- shell_out_with_timeout!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}").stdout.each_line do |line|
+ logger.trace("#{new_resource} checking rpm status")
+ shell_out!("rpm", "-qp", "--queryformat", "%{NAME} %{VERSION}-%{RELEASE}\n", new_resource.source).stdout.each_line do |line|
case line
when /^(\S+)\s(\S+)$/
- @current_resource.package_name($1)
- @new_resource.version($2)
+ current_resource.package_name($1)
+ new_resource.version($2)
@candidate_version = $2
end
end
else
- if Array(@new_resource.action).include?(:install)
+ if Array(new_resource.action).include?(:install)
@package_source_exists = false
return
end
end
- Chef::Log.debug("#{@new_resource} checking install state")
- @rpm_status = shell_out_with_timeout("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@current_resource.package_name}")
+ logger.trace("#{new_resource} checking install state")
+ @rpm_status = shell_out("rpm", "-q", "--queryformat", "%{NAME} %{VERSION}-%{RELEASE}\n", current_resource.package_name)
@rpm_status.stdout.each_line do |line|
case line
when /^(\S+)\s(\S+)$/
- Chef::Log.debug("#{@new_resource} current version is #{$2}")
- @current_resource.version($2)
+ logger.trace("#{new_resource} current version is #{$2}")
+ current_resource.version($2)
end
end
- @current_resource
+ current_resource
end
def install_package(name, version)
- unless @current_resource.version
- shell_out_with_timeout!( "rpm #{@new_resource.options} -i #{@new_resource.source}" )
- else
+ if current_resource.version
if allow_downgrade
- shell_out_with_timeout!( "rpm #{@new_resource.options} -U --oldpackage #{@new_resource.source}" )
+ shell_out!("rpm", options, "-U", "--oldpackage", new_resource.source)
else
- shell_out_with_timeout!( "rpm #{@new_resource.options} -U #{@new_resource.source}" )
+ shell_out!("rpm", options, "-U", new_resource.source)
end
+ else
+ shell_out!("rpm", options, "-i", new_resource.source)
end
end
- alias_method :upgrade_package, :install_package
+ alias upgrade_package install_package
def remove_package(name, version)
if version
- shell_out_with_timeout!( "rpm #{@new_resource.options} -e #{name}-#{version}" )
+ shell_out!("rpm", options, "-e", "#{name}-#{version}")
else
- shell_out_with_timeout!( "rpm #{@new_resource.options} -e #{name}" )
+ shell_out!("rpm", options, "-e", name)
end
end
private
+ def version_compare(v1, v2)
+ Chef::Provider::Package::Yum::RPMVersion.parse(v1) <=> Chef::Provider::Package::Yum::RPMVersion.parse(v2)
+ end
+
def uri_scheme?(str)
scheme = URI.split(str).first
return false unless scheme
+
%w{http https ftp file}.include?(scheme.downcase)
rescue URI::InvalidURIError
- return false
+ false
end
end
end
diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb
index 187197d143..a0b569b8e3 100644
--- a/lib/chef/provider/package/rubygems.rb
+++ b/lib/chef/provider/package/rubygems.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2008-2016, 2010-2016 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,79 +17,97 @@
# 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"
+autoload :URI, "uri"
+require_relative "../package"
+require_relative "../../resource/package"
+require_relative "../../mixin/get_source_from_package"
+require_relative "../../mixin/which"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
# Class methods on Gem are defined in rubygems
-require "rubygems"
+autoload :Gem, "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"
-require "rubygems/package"
-require "rubygems/dependency_installer"
-require "rubygems/uninstaller"
-require "rubygems/specification"
+Gem.autoload :Version, "rubygems/version"
+Gem.autoload :Dependency, "rubygems/dependency"
+Gem.autoload :SpecFetcher, "rubygems/spec_fetcher"
+Gem.autoload :Platform, "rubygems/platform"
+Gem.autoload :Package, "rubygems/package"
+Gem.autoload :DependencyInstaller, "rubygems/dependency_installer"
+Gem.autoload :Uninstaller, "rubygems/uninstaller"
+Gem.autoload :Specification, "rubygems/specification"
class Chef
class Provider
class Package
class Rubygems < Chef::Provider::Package
class GemEnvironment
- # HACK: trigger gem config load early. Otherwise it can get lazy
- # loaded during operations where we've set Gem.sources to an
- # alternate value and overwrite it with the defaults.
- Gem.configuration
+ DEFAULT_UNINSTALLER_OPTS = { ignore: true, executables: true }.freeze
- DEFAULT_UNINSTALLER_OPTS = { :ignore => true, :executables => true }
+ def initialize(*args)
+ super
+ # HACK: trigger gem config load early. Otherwise it can get lazy
+ # loaded during operations where we've set Gem.sources to an
+ # alternate value and overwrite it with the defaults.
+ Gem.configuration
+ end
- ##
# The paths where rubygems should search for installed gems.
# Implemented by subclasses.
def gem_paths
raise NotImplementedError
end
- ##
# A rubygems source index containing the list of gemspecs for all
# available gems in the gem installation.
# Implemented by subclasses
- # === Returns
- # Gem::SourceIndex
+ #
+ # @return [Gem::SourceIndex]
+ #
def gem_source_index
raise NotImplementedError
end
- ##
# A rubygems specification object containing the list of gemspecs for all
# available gems in the gem installation.
# Implemented by subclasses
- # For rubygems >= 1.8.0
- # === Returns
- # Gem::Specification
+ #
+ # @return [Gem::Specification]
+ #
def gem_specification
raise NotImplementedError
end
- ##
+ def rubygems_version
+ raise NotImplementedError
+ end
+
# Lists the installed versions of +gem_name+, constrained by the
# version spec in +gem_dep+
- # === Arguments
- # Gem::Dependency +gem_dep+ is a Gem::Dependency object, its version
- # specification constrains which gems are returned.
- # === Returns
- # [Gem::Specification] an array of Gem::Specification objects
+ #
+ # @param gem_dep [Gem::Dependency] the version specification that constrains
+ # which gems are used.
+ # @return [Array<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")
+ rubygems_version = Gem::Version.new(Gem::VERSION)
+ if rubygems_version >= Gem::Version.new("2.7")
+ # In newer Rubygems, bundler is now a "default gem" which means
+ # even with AlternateGemEnvironment when you try to get the
+ # installed versions, you get the one from Chef's Ruby's default
+ # gems. This workaround ignores default gems entirely so we see
+ # only the installed gems.
+ stubs = gem_specification.send(:installed_stubs, gem_specification.dirs, "#{gem_dep.name}-*.gemspec")
+ # Filter down to only to only stubs we actually want. The name
+ # filter is needed in case of things like `foo-*.gemspec` also
+ # matching a gem named `foo-bar`.
+ stubs.select! { |stub| stub.name == gem_dep.name && gem_dep.requirement.satisfied_by?(stub.version) }
+ # This isn't sorting before returning because the only code that
+ # uses this method calls `max_by` so it doesn't need to be sorted.
+ stubs
+ else # >= rubygems 1.8 behavior
gem_specification.find_all_by_name(gem_dep.name, gem_dep.requirement)
- else
- gem_source_index.search(gem_dep)
end
end
@@ -133,11 +151,11 @@ class Chef
def candidate_version_from_file(gem_dependency, source)
spec = spec_from_file(source)
if spec.satisfies_requirement?(gem_dependency)
- logger.debug { "#{@new_resource} found candidate gem version #{spec.version} from local gem package #{source}" }
+ logger.trace { "found candidate gem version #{spec.version} from local gem package #{source}" }
spec.version
else
# This is probably going to end badly...
- logger.warn { "#{@new_resource} gem package #{source} does not satisfy the requirements #{gem_dependency}" }
+ logger.warn { "gem package #{source} does not satisfy the requirements #{gem_dependency}" }
nil
end
end
@@ -170,7 +188,7 @@ class Chef
# Use the API that 'gem install' calls which does not pull down the rubygems universe
begin
rs = dependency_installer.resolve_dependencies gem_dependency.name, gem_dependency.requirement
- rs.specs.select { |s| s.name == gem_dependency.name }.first
+ rs.specs.find { |s| s.name == gem_dependency.name }
rescue Gem::UnsatisfiableDependencyError
nil
end
@@ -178,11 +196,11 @@ class Chef
version = spec && spec.version
if version
- logger.debug { "#{@new_resource} found gem #{spec.name} version #{version} for platform #{spec.platform} from #{source}" }
+ logger.trace { "found gem #{spec.name} version #{version} for platform #{spec.platform} from #{source}" }
version
else
- source_list = sources.compact.empty? ? "[#{Gem.sources.to_a.join(', ')}]" : "[#{sources.join(', ')}]"
- logger.warn { "#{@new_resource} failed to find gem #{gem_dependency} from #{source_list}" }
+ source_list = sources.compact.empty? ? "[#{Gem.sources.to_a.join(", ")}]" : "[#{sources.join(", ")}]"
+ logger.warn { "failed to find gem #{gem_dependency} from #{source_list}" }
nil
end
end
@@ -217,7 +235,7 @@ class Chef
# Set rubygems' user interaction to ConsoleUI or SilentUI depending
# on our current debug level
def with_correct_verbosity
- Gem::DefaultUserInteraction.ui = Chef::Log.debug? ? Gem::ConsoleUI.new : Gem::SilentUI.new
+ Gem::DefaultUserInteraction.ui = logger.trace? ? Gem::ConsoleUI.new : Gem::SilentUI.new
yield
end
@@ -232,7 +250,7 @@ class Chef
private
def logger
- Chef::Log.logger
+ Chef::Log.with_child({ subsystem: "gem_installer_environment" })
end
end
@@ -251,6 +269,10 @@ class Chef
Gem::Specification
end
+ def rubygems_version
+ Gem::VERSION
+ end
+
def candidate_version_from_remote(gem_dependency, *sources)
with_gem_sources(*sources) do
find_newest_remote_version(gem_dependency, *sources)
@@ -260,7 +282,7 @@ class Chef
end
class AlternateGemEnvironment < GemEnvironment
- JRUBY_PLATFORM = /(:?universal|x86_64|x86)\-java\-[0-9\.]+/
+ JRUBY_PLATFORM = /(:?universal|x86_64|x86)\-java\-[0-9\.]+/.freeze
def self.gempath_cache
@gempath_cache ||= {}
@@ -278,6 +300,10 @@ class Chef
@gem_binary_location = gem_binary_location
end
+ def rubygems_version
+ @rubygems_version ||= shell_out!("#{@gem_binary_location} --version").stdout.chomp
+ end
+
def gem_paths
if self.class.gempath_cache.key?(@gem_binary_location)
self.class.gempath_cache[@gem_binary_location]
@@ -285,7 +311,7 @@ class Chef
# shellout! is a fork/exec which won't work on windows
shell_style_paths = shell_out!("#{@gem_binary_location} env gempath").stdout
# on windows, the path separator is (usually? always?) semicolon
- paths = shell_style_paths.split(::File::PATH_SEPARATOR).map { |path| path.strip }
+ paths = shell_style_paths.split(::File::PATH_SEPARATOR).map(&:strip)
self.class.gempath_cache[@gem_binary_location] = paths
end
end
@@ -322,11 +348,11 @@ class Chef
self.class.platform_cache[@gem_binary_location]
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)]
- else
- self.class.platform_cache[@gem_binary_location] = Gem.platforms
- end
+ self.class.platform_cache[@gem_binary_location] = if jruby = gem_environment[JRUBY_PLATFORM]
+ ["ruby", Gem::Platform.new(jruby)]
+ else
+ Gem.platforms
+ end
end
end
@@ -352,57 +378,54 @@ class Chef
attr_reader :gem_env
attr_reader :cleanup_gem_env
- def logger
- Chef::Log.logger
- end
-
provides :chef_gem
provides :gem_package
include Chef::Mixin::GetSourceFromPackage
+ include Chef::Mixin::Which
def initialize(new_resource, run_context = nil)
super
@cleanup_gem_env = true
if new_resource.gem_binary
- if new_resource.options && new_resource.options.kind_of?(Hash)
+ if new_resource.options && new_resource.options.is_a?(Hash)
msg = "options cannot be given as a hash when using an explicit gem_binary\n"
msg << "in #{new_resource} from #{new_resource.source_line}"
raise ArgumentError, msg
end
@gem_env = AlternateGemEnvironment.new(new_resource.gem_binary)
- Chef::Log.debug("#{@new_resource} using gem '#{new_resource.gem_binary}'")
- elsif is_omnibus? && (!@new_resource.instance_of? Chef::Resource::ChefGem)
+ logger.trace("#{new_resource} using gem '#{new_resource.gem_binary}'")
+ elsif is_omnibus? && (!new_resource.instance_of? Chef::Resource::ChefGem)
# Opscode Omnibus - The ruby that ships inside omnibus is only used for Chef
# Default to installing somewhere more functional
- if new_resource.options && new_resource.options.kind_of?(Hash)
+ if new_resource.options && new_resource.options.is_a?(Hash)
msg = [
"Gem options must be passed to gem_package as a string instead of a hash when",
- "using this installation of Chef because it runs with its own packaged Ruby. A hash",
- "may only be used when installing a gem to the same Ruby installation that Chef is",
- "running under. See https://docs.chef.io/resource_gem_package.html for more information.",
+ "using this installation of #{ChefUtils::Dist::Infra::PRODUCT} because it runs with its own packaged Ruby. A hash",
+ "may only be used when installing a gem to the same Ruby installation that #{ChefUtils::Dist::Infra::PRODUCT} is",
+ "running under. See https://docs.chef.io/resources/gem_package/ for more information.",
"Error raised at #{new_resource} from #{new_resource.source_line}",
].join("\n")
raise ArgumentError, msg
end
gem_location = find_gem_by_path
- @new_resource.gem_binary gem_location
+ new_resource.gem_binary gem_location
@gem_env = AlternateGemEnvironment.new(gem_location)
- Chef::Log.debug("#{@new_resource} using gem '#{gem_location}'")
+ logger.trace("#{new_resource} using gem '#{gem_location}'")
else
@gem_env = CurrentGemEnvironment.new
@cleanup_gem_env = false
- Chef::Log.debug("#{@new_resource} using gem from running ruby environment")
+ logger.trace("#{new_resource} using gem from running ruby environment")
end
end
def is_omnibus?
- if RbConfig::CONFIG["bindir"] =~ %r{/(opscode|chef|chefdk)/embedded/bin}
- Chef::Log.debug("#{@new_resource} detected omnibus installation in #{RbConfig::CONFIG['bindir']}")
+ if %r{/(opscode|chef|chefdk)/embedded/bin}.match?(RbConfig::CONFIG["bindir"])
+ logger.trace("#{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"
- Chef::Log.debug("#{@new_resource} detected omnibus installation in #{RbConfig::CONFIG['bindir']}")
+ elsif RbConfig::CONFIG["bindir"].sub(/^\w:/, "") == "/opscode/chef/embedded/bin"
+ logger.trace("#{new_resource} detected omnibus installation in #{RbConfig::CONFIG["bindir"]}")
# windows, with the drive letter removed
true
else
@@ -411,50 +434,41 @@ class Chef
end
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).find { |path| ::File.exists?(path + separator + "gem") }
- 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"
+ which("gem", extra_path: RbConfig::CONFIG["bindir"])
end
def gem_dependency
- Gem::Dependency.new(@new_resource.package_name, @new_resource.version)
+ Gem::Dependency.new(new_resource.package_name, new_resource.version)
end
def source_is_remote?
- return true if @new_resource.source.nil?
- scheme = URI.parse(@new_resource.source).scheme
+ return true if new_resource.source.nil?
+ return true if new_resource.source.is_a?(Array)
+
+ scheme = URI.parse(new_resource.source).scheme
# URI.parse gets confused by MS Windows paths with forward slashes.
- scheme = nil if scheme =~ /^[a-z]$/
+ scheme = nil if /^[a-z]$/.match?(scheme)
%w{http https}.include?(scheme)
rescue URI::InvalidURIError
- Chef::Log.debug("#{@new_resource} failed to parse source '#{@new_resource.source}' as a URI, assuming a local path")
+ logger.trace("#{new_resource} failed to parse source '#{new_resource.source}' as a URI, assuming a local path")
false
end
def current_version
- # rubygems 2.6.3 ensures that gem lists are sorted newest first
- pos = if Gem::Version.new(Gem::VERSION) >= Gem::Version.new("2.6.3")
- :first
- else
- :last
- end
-
# If one or more matching versions are installed, the newest of them
# is the current version
if !matching_installed_versions.empty?
- gemspec = matching_installed_versions.send(pos)
- logger.debug { "#{@new_resource} found installed gem #{gemspec.name} version #{gemspec.version} matching #{gem_dependency}" }
+ gemspec = matching_installed_versions.max_by(&:version)
+ logger.trace { "#{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.
elsif !all_installed_versions.empty?
- gemspec = all_installed_versions.send(pos)
- logger.debug { "#{@new_resource} newest installed version of gem #{gemspec.name} is #{gemspec.version}" }
+ gemspec = all_installed_versions.max_by(&:version)
+ logger.trace { "#{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}" }
+ logger.trace { "#{new_resource} no installed version found for #{gem_dependency}" }
nil
end
end
@@ -469,22 +483,38 @@ class Chef
end
end
+ ##
+ # If `include_default_source` is nil, return true if the global
+ # `rubygems_url` was set or if `clear_sources` and `source` on the
+ # resource are not set.
+ # If `include_default_source` is not nil, it has been set explicitly on
+ # the resource and that value should be used.
+ def include_default_source?
+ if new_resource.include_default_source.nil?
+ !!Chef::Config[:rubygems_url] || !(new_resource.source || new_resource.clear_sources)
+ else
+ new_resource.include_default_source
+ end
+ end
+
def gem_sources
- @new_resource.source ? Array(@new_resource.source) : nil
+ srcs = [ new_resource.source ]
+ srcs << (Chef::Config[:rubygems_url] || "https://rubygems.org") if include_default_source?
+ srcs.flatten.compact
end
def load_current_resource
- @current_resource = Chef::Resource::Package::GemPackage.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
+ @current_resource = Chef::Resource::Package::GemPackage.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
if current_spec = current_version
- @current_resource.version(current_spec.version.to_s)
+ current_resource.version(current_spec.version.to_s)
end
- @current_resource
+ current_resource
end
def cleanup_after_converge
if @cleanup_gem_env
- logger.debug { "#{@new_resource} resetting gem environment to default" }
+ logger.trace { "#{new_resource} resetting gem environment to default" }
Gem.clear_paths
end
end
@@ -494,13 +524,14 @@ class Chef
if 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
+ @gem_env.candidate_version_from_file(gem_dependency, new_resource.source).to_s
end
end
end
def version_requirement_satisfied?(current_version, new_version)
return false unless current_version && new_version
+
Gem::Requirement.new(new_version).satisfied_by?(Gem::Version.new(current_version))
end
@@ -511,18 +542,18 @@ class Chef
# 2. shell out to `gem install` when a String of options is given
# 3. use gems API with options if a hash of options is given
def install_package(name, version)
- if source_is_remote? && @new_resource.gem_binary.nil?
- if @new_resource.options.nil?
- @gem_env.install(gem_dependency, :sources => gem_sources)
- elsif @new_resource.options.kind_of?(Hash)
- options = @new_resource.options
+ if source_is_remote? && new_resource.gem_binary.nil?
+ if new_resource.options.nil?
+ @gem_env.install(gem_dependency, sources: gem_sources)
+ elsif new_resource.options.is_a?(Hash)
+ options = new_resource.options
options[:sources] = gem_sources
@gem_env.install(gem_dependency, options)
else
install_via_gem_command(name, version)
end
- elsif @new_resource.gem_binary.nil?
- @gem_env.install(@new_resource.source)
+ elsif new_resource.gem_binary.nil?
+ @gem_env.install(new_resource.source)
else
install_via_gem_command(name, version)
end
@@ -530,23 +561,35 @@ class Chef
end
def gem_binary_path
- @new_resource.gem_binary || "gem"
+ new_resource.gem_binary || "gem"
+ end
+
+ ##
+ # If `clear_sources` is nil, clearing sources is implied if a `source`
+ # was added or if the global rubygems URL is set. If `clear_sources`
+ # is not nil, it has been set explicitly on the resource and its value
+ # should be used.
+ def clear_sources?
+ if new_resource.clear_sources.nil?
+ !!(new_resource.source || Chef::Config[:rubygems_url])
+ else
+ new_resource.clear_sources
+ end
end
def install_via_gem_command(name, version)
- if @new_resource.source =~ /\.gem$/i
- name = @new_resource.source
- src = " --local" unless source_is_remote?
- elsif @new_resource.clear_sources
- src = " --clear-sources"
- src << (@new_resource.source && " --source=#{@new_resource.source}" || "")
+ src = []
+ if new_resource.source.is_a?(String) && new_resource.source =~ /\.gem$/i
+ name = new_resource.source
else
- src = @new_resource.source && " --source=#{@new_resource.source} --source=#{Chef::Config[:rubygems_url]}"
+ src << "--clear-sources" if clear_sources?
+ src += gem_sources.map { |s| "--source=#{s}" }
end
- if !version.nil? && version.length > 0
- shell_out_with_timeout!("#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src}#{opts}", :env => nil)
+ src_str = src.empty? ? "" : " #{src.join(" ")}"
+ if !version.nil? && !version.empty?
+ shell_out!("#{gem_binary_path} install #{name} -q #{rdoc_string} -v \"#{version}\"#{src_str}#{opts}", env: nil)
else
- shell_out_with_timeout!("#{gem_binary_path} install \"#{name}\" -q --no-rdoc --no-ri #{src}#{opts}", :env => nil)
+ shell_out!("#{gem_binary_path} install \"#{name}\" -q #{rdoc_string} #{src_str}#{opts}", env: nil)
end
end
@@ -555,11 +598,11 @@ class Chef
end
def remove_package(name, version)
- if @new_resource.gem_binary.nil?
- if @new_resource.options.nil?
+ if new_resource.gem_binary.nil?
+ if new_resource.options.nil?
@gem_env.uninstall(name, version)
- elsif @new_resource.options.kind_of?(Hash)
- @gem_env.uninstall(name, version, @new_resource.options)
+ elsif new_resource.options.is_a?(Hash)
+ @gem_env.uninstall(name, version, new_resource.options)
else
uninstall_via_gem_command(name, version)
end
@@ -570,9 +613,9 @@ class Chef
def uninstall_via_gem_command(name, version)
if version
- shell_out_with_timeout!("#{gem_binary_path} uninstall #{name} -q -x -I -v \"#{version}\"#{opts}", :env => nil)
+ shell_out!("#{gem_binary_path} uninstall #{name} -q -x -I -v \"#{version}\"#{opts}", env: nil)
else
- shell_out_with_timeout!("#{gem_binary_path} uninstall #{name} -q -x -I -a#{opts}", :env => nil)
+ shell_out!("#{gem_binary_path} uninstall #{name} -q -x -I -a#{opts}", env: nil)
end
end
@@ -582,8 +625,20 @@ class Chef
private
+ def rdoc_string
+ if needs_nodocument?
+ "--no-document"
+ else
+ "--no-rdoc --no-ri"
+ end
+ end
+
+ def needs_nodocument?
+ Gem::Requirement.new(">= 3.0.0.beta1").satisfied_by?(Gem::Version.new(gem_env.rubygems_version))
+ end
+
def opts
- expand_options(@new_resource.options)
+ expand_options(new_resource.options)
end
end
diff --git a/lib/chef/provider/package/smartos.rb b/lib/chef/provider/package/smartos.rb
index 3f09bef212..a44280ec75 100644
--- a/lib/chef/provider/package/smartos.rb
+++ b/lib/chef/provider/package/smartos.rb
@@ -3,7 +3,7 @@
# Bryan McLellan (btm@loftninjas.org)
# Matthew Landauer (matthew@openaustralia.org)
# Ben Rockwood (benr@joyent.com)
-# Copyright:: Copyright 2009-2016, Bryan McLellan, Matthew Landauer
+# Copyright:: Copyright 2009-2018, Bryan McLellan, Matthew Landauer
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,9 +19,9 @@
# limitations under the License.
#
-require "chef/provider/package"
-require "chef/resource/package"
-require "chef/mixin/get_source_from_package"
+require_relative "../package"
+require_relative "../../resource/package"
+require_relative "../../mixin/get_source_from_package"
class Chef
class Provider
@@ -30,35 +30,36 @@ class Chef
attr_accessor :is_virtual_package
provides :package, platform: "smartos"
- provides :smartos_package, os: "solaris2", platform_family: "smartos"
+ provides :smartos_package
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)
- check_package_state(@new_resource.package_name)
- @current_resource # modified by check_package_state
+ logger.trace("#{new_resource} loading current resource")
+ @current_resource = Chef::Resource::Package.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
+ check_package_state(new_resource.package_name)
+ current_resource # modified by check_package_state
end
def check_package_state(name)
- Chef::Log.debug("#{@new_resource} checking package #{name}")
+ logger.trace("#{new_resource} checking package #{name}")
version = nil
- info = shell_out_with_timeout!("/opt/local/sbin/pkg_info", "-E", "#{name}*", :env => nil, :returns => [0, 1])
+ info = shell_out!("/opt/local/sbin/pkg_info", "-E", "#{name}*", env: nil, returns: [0, 1])
if info.stdout
- version = info.stdout[/^#{@new_resource.package_name}-(.+)/, 1]
+ version = info.stdout[/^#{new_resource.package_name}-(.+)/, 1]
end
if version
- @current_resource.version(version)
+ current_resource.version(version)
end
end
def candidate_version
return @candidate_version if @candidate_version
+
name = nil
version = nil
- pkg = shell_out_with_timeout!("/opt/local/bin/pkgin", "se", new_resource.package_name, :env => nil, :returns => [0, 1])
+ pkg = shell_out!("/opt/local/bin/pkgin", "se", new_resource.package_name, env: nil, returns: [0, 1])
pkg.stdout.each_line do |line|
case line
when /^#{new_resource.package_name}/
@@ -70,20 +71,20 @@ class Chef
end
def install_package(name, version)
- Chef::Log.debug("#{@new_resource} installing package #{name} version #{version}")
+ logger.trace("#{new_resource} installing package #{name} version #{version}")
package = "#{name}-#{version}"
- out = shell_out_with_timeout!("/opt/local/bin/pkgin", "-y", "install", package, :env => nil)
+ out = shell_out!("/opt/local/bin/pkgin", "-y", "install", package, env: nil)
end
def upgrade_package(name, version)
- Chef::Log.debug("#{@new_resource} upgrading package #{name} version #{version}")
+ logger.trace("#{new_resource} upgrading package #{name} version #{version}")
install_package(name, version)
end
def remove_package(name, version)
- Chef::Log.debug("#{@new_resource} removing package #{name} version #{version}")
- package = "#{name}"
- out = shell_out_with_timeout!("/opt/local/bin/pkgin", "-y", "remove", package, :env => nil)
+ logger.trace("#{new_resource} removing package #{name} version #{version}")
+ package = name.to_s
+ out = shell_out!("/opt/local/bin/pkgin", "-y", "remove", package, env: nil)
end
end
diff --git a/lib/chef/provider/package/snap.rb b/lib/chef/provider/package/snap.rb
new file mode 100644
index 0000000000..81af09e04d
--- /dev/null
+++ b/lib/chef/provider/package/snap.rb
@@ -0,0 +1,427 @@
+#
+# Author:: S.Cavallo (<smcavallo@hotmail.com>)
+# Copyright:: Copyright (c) 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_relative "../package"
+require_relative "../../resource/snap_package"
+require_relative "../../mixin/shell_out"
+require "socket" unless defined?(Socket)
+require "json" unless defined?(JSON)
+
+class Chef
+ class Provider
+ class Package
+ class Snap < Chef::Provider::Package
+ allow_nils
+ use_multipackage_api
+ use_package_name_for_source
+
+ provides :snap_package
+
+ def load_current_resource
+ @current_resource = Chef::Resource::SnapPackage.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
+ current_resource.version(get_current_versions)
+
+ current_resource
+ end
+
+ def define_resource_requirements
+ requirements.assert(:install, :upgrade, :remove, :purge) do |a|
+ a.assertion { !new_resource.source || ::File.exist?(new_resource.source) }
+ a.failure_message Chef::Exceptions::Package, "Package #{new_resource.package_name} not found: #{new_resource.source}"
+ a.whyrun "assuming #{new_resource.source} would have previously been created"
+ end
+
+ super
+ end
+
+ def candidate_version
+ package_name_array.each_with_index.map do |pkg, i|
+ available_version(i)
+ end
+ end
+
+ def get_current_versions
+ package_name_array.each_with_index.map do |pkg, i|
+ installed_version(i)
+ end.compact
+ end
+
+ def install_package(names, versions)
+ if new_resource.source
+ install_snap_from_source(names, new_resource.source)
+ else
+ install_snaps(names)
+ end
+ end
+
+ def upgrade_package(names, versions)
+ if new_resource.source
+ install_snap_from_source(names, new_resource.source)
+ else
+ if get_current_versions.empty?
+ install_snaps(names, versions)
+ else
+ update_snaps(names)
+ end
+ end
+ end
+
+ def remove_package(names, versions)
+ uninstall_snaps(names)
+ end
+
+ alias purge_package remove_package
+
+ private
+
+ # @return Array<Version>
+ def available_version(index)
+ @available_version ||= []
+
+ @available_version[index] ||= if new_resource.source
+ get_snap_version_from_source(new_resource.source)
+ else
+ get_latest_package_version(package_name_array[index], new_resource.channel)
+ end
+
+ @available_version[index]
+ end
+
+ # @return [Array<Version>]
+ def installed_version(index)
+ @installed_version ||= []
+ @installed_version[index] ||= get_installed_package_version_by_name(package_name_array[index])
+ @installed_version[index]
+ end
+
+ def safe_version_array
+ if new_resource.version.is_a?(Array)
+ new_resource.version
+ elsif new_resource.version.nil?
+ package_name_array.map { nil }
+ else
+ [new_resource.version]
+ end
+ end
+
+ # ToDo: Support authentication
+ # ToDo: Support private snap repos
+ # https://github.com/snapcore/snapd/wiki/REST-API
+
+ # ToDo: Would prefer to use net/http over socket
+ def call_snap_api(method, uri, post_data = nil?)
+ request = "#{method} #{uri} HTTP/1.0\r\n" +
+ "Accept: application/json\r\n" +
+ "Content-Type: application/json\r\n"
+ if method == "POST"
+ pdata = post_data.to_json.to_s
+ request.concat("Content-Length: #{pdata.bytesize}\r\n\r\n#{pdata}")
+ end
+ request.concat("\r\n")
+
+ # while it is expected to allow clients to connect using https over
+ # a tcp socket, at this point only a unix socket is supported. the
+ # socket is /run/snapd.socket note - UNIXSocket is not defined on
+ # windows systems
+ if defined?(::UNIXSocket)
+ UNIXSocket.open("/run/snapd.socket") do |socket|
+ # send request, read the response, split the response and parse
+ # the body
+ socket.write(request)
+
+ # WARNING!!! HERE BE DRAGONs
+ #
+ # So snapd doesn't return an EOF at the end of its body, so
+ # doing a normal read will just hang forever.
+ #
+ # Well, sort of. if, after it writes everything, you then send
+ # yet-another newline, it'll then send its EOF and promptly
+ # disconnect closing the pipe and preventing reading. so, you
+ # have to read first, and therein lies the EOF problem.
+ #
+ # So you can do non-blocking reads with selects, but it
+ # makes every read take about 5 seconds. If, instead, we
+ # read the last line char-by-char, it's about half a second.
+ #
+ # Reading a character at a time isn't efficient, and since we
+ # know that http headers always have a blank line after them,
+ # we can read lines until we find a blank line and *then* read
+ # a character at a time. snap returns all the json on a single
+ # line, so once you pass headers you must read a character a
+ # time.
+ #
+ # - jaymzh
+
+ Chef::Log.trace(
+ "snap_package[#{new_resource.package_name}]: reading headers"
+ )
+ loop do
+ response = socket.readline
+ break if response.strip.empty? # finished headers
+ end
+ Chef::Log.trace(
+ "snap_package[#{new_resource.package_name}]: past headers, " +
+ "onto the body..."
+ )
+ result = nil
+ body = ""
+ socket.each_char do |c|
+ body << c
+ # we know we're not done if we don't have a char that
+ # can end JSON
+ next unless ["}", "]"].include?(c)
+
+ begin
+ result = JSON.parse(body)
+ # if we get here, we were able to parse the json so we
+ # are done reading
+ break
+ rescue JSON::ParserError
+ next
+ end
+ end
+ result
+ end
+ end
+ end
+
+ def get_change_id(id)
+ call_snap_api("GET", "/v2/changes/#{id}")
+ end
+
+ def get_id_from_async_response(response)
+ if response["type"] == "error"
+ raise "status: #{response["status"]}, kind: #{response["result"]["kind"]}, message: #{response["result"]["message"]}"
+ end
+
+ response["change"]
+ end
+
+ def wait_for_completion(id)
+ n = 0
+ waiting = true
+ while waiting
+ result = get_change_id(id)
+ case result["result"]["status"]
+ when "Do", "Doing", "Undoing", "Undo"
+ # Continue
+ when "Abort", "Hold", "Error"
+ raise result
+ when "Done"
+ waiting = false
+ else
+ # How to handle unknown status
+ end
+ n += 1
+ raise "Snap operating timed out after #{n} seconds." if n == 300
+
+ sleep(1)
+ end
+ end
+
+ def snapctl(*args)
+ shell_out!("snap", *args)
+ end
+
+ def get_snap_version_from_source(path)
+ body = {
+ "context-id" => "get_snap_version_from_source_#{path}",
+ "args" => ["info", path],
+ }.to_json
+
+ # json = call_snap_api('POST', '/v2/snapctl', body)
+ response = snapctl(["info", path])
+ Chef::Log.trace(response)
+ response.error!
+ get_version_from_stdout(response.stdout)
+ end
+
+ def get_version_from_stdout(stdout)
+ stdout.match(/version: (\S+)/)[1]
+ end
+
+ def install_snap_from_source(name, path)
+ # json = call_snap_api('POST', '/v2/snapctl', body)
+ response = snapctl(["install", path])
+ Chef::Log.trace(response)
+ response.error!
+ end
+
+ def install_snaps(snap_names, versions)
+ snap_names.each do |snap|
+ response = post_snap(snap, "install", new_resource.channel, new_resource.options)
+ id = get_id_from_async_response(response)
+ wait_for_completion(id)
+ end
+ end
+
+ def update_snaps(snap_names)
+ response = post_snaps(snap_names, "refresh", nil, new_resource.options)
+ id = get_id_from_async_response(response)
+ wait_for_completion(id)
+ end
+
+ def uninstall_snaps(snap_names)
+ response = post_snaps(snap_names, "remove", nil, new_resource.options)
+ id = get_id_from_async_response(response)
+ wait_for_completion(id)
+ end
+
+ # Constructs the multipart/form-data required to sideload packages
+ # https://github.com/snapcore/snapd/wiki/REST-API#sideload-request
+ #
+ # @param snap_name [String] An array of snap package names to install
+ # @param action [String] The action. Valid: install or try
+ # @param options [Hash] Misc configuration Options
+ # @param path [String] Path to the package on disk
+ # @param content_length [Integer] byte size of the snap file
+ def generate_multipart_form_data(snap_name, action, options, path, content_length)
+ snap_options = options.map do |k, v|
+ <<~SNAP_OPTION
+ Content-Disposition: form-data; name="#{k}"
+
+ #{v}
+ --#{snap_name}
+ SNAP_OPTION
+ end
+
+ <<~SNAP_S
+ Host:
+ Content-Type: multipart/form-data; boundary=#{snap_name}
+ Content-Length: #{content_length}
+
+ --#{snap_name}
+ Content-Disposition: form-data; name="action"
+
+ #{action}
+ --#{snap_name}
+ #{snap_options.join("\n").chomp}
+ Content-Disposition: form-data; name="snap"; filename="#{path}"
+
+ <#{content_length} bytes of snap file data>
+ --#{snap_name}
+ SNAP_S
+ end
+
+ # Constructs json to post for snap changes
+ #
+ # @param snap_names [Array] An array of snap package names to install
+ # @param action [String] The action. install, refresh, remove, revert, enable, disable or switch
+ # @param channel [String] The release channel. Ex. stable
+ # @param options [Hash] Misc configuration Options
+ # @param revision [String] A revision/version
+ def generate_snap_json(snap_names, action, channel, options, revision = nil)
+ request = {
+ "action" => action,
+ "snaps" => snap_names,
+ }
+ if %w{install refresh switch}.include?(action) && channel
+ request["channel"] = channel
+ end
+
+ # No defensive handling of params
+ # Snap will throw the proper exception if called improperly
+ # And we can provide that exception to the end user
+ if options
+ request["classic"] = true if options.include?("classic")
+ request["devmode"] = true if options.include?("devmode")
+ request["jailmode"] = true if options.include?("jailmode")
+ request["ignore_validation"] = true if options.include?("ignore-validation")
+ end
+ request["revision"] = revision unless revision.nil?
+ request
+ end
+
+ # Post to the snap api to update snaps
+ #
+ # @param snap_names [Array] An array of snap package names to install
+ # @param action [String] The action. install, refresh, remove, revert, enable, disable or switch
+ # @param channel [String] The release channel. Ex. stable
+ # @param options [Hash] Misc configuration Options
+ # @param revision [String] A revision/version
+ def post_snaps(snap_names, action, channel, options, revision = nil)
+ json = generate_snap_json(snap_names, action, channel, options, revision = nil)
+ call_snap_api("POST", "/v2/snaps", json)
+ end
+
+ def post_snap(snap_name, action, channel, options, revision = nil)
+ json = generate_snap_json(snap_name, action, channel, options, revision = nil)
+ json.delete("snaps")
+ call_snap_api("POST", "/v2/snaps/#{snap_name}", json)
+ end
+
+ def get_latest_package_version(name, channel)
+ json = call_snap_api("GET", "/v2/find?name=#{name}")
+ if json["status-code"] != 200
+ raise Chef::Exceptions::Package, json["result"], caller
+ end
+
+ unless json["result"][0]["channels"]["latest/#{channel}"]
+ raise Chef::Exceptions::Package, "No version of #{name} in channel #{channel}", caller
+ end
+
+ # Return the version matching the channel
+ json["result"][0]["channels"]["latest/#{channel}"]["version"]
+ end
+
+ def get_installed_packages
+ json = call_snap_api("GET", "/v2/snaps")
+ # We only allow 200 or 404s
+ unless [200, 404].include? json["status-code"]
+ raise Chef::Exceptions::Package, json["result"], caller
+ end
+
+ json["result"]
+ end
+
+ def get_installed_package_version_by_name(name)
+ result = get_installed_package_by_name(name)
+ # Return nil if not installed
+ if result["status-code"] == 404
+ nil
+ else
+ result["version"]
+ end
+ end
+
+ def get_installed_package_by_name(name)
+ json = call_snap_api("GET", "/v2/snaps/#{name}")
+ # We only allow 200 or 404s
+ unless [200, 404].include? json["status-code"]
+ raise Chef::Exceptions::Package, json["result"], caller
+ end
+
+ json["result"]
+ end
+
+ def get_installed_package_conf(name)
+ json = call_snap_api("GET", "/v2/snaps/#{name}/conf")
+ json["result"]
+ end
+
+ def set_installed_package_conf(name, value)
+ response = call_snap_api("PUT", "/v2/snaps/#{name}/conf", value)
+ id = get_id_from_async_response(response)
+ wait_for_completion(id)
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/solaris.rb b/lib/chef/provider/package/solaris.rb
index 1c393e6a20..7094428236 100644
--- a/lib/chef/provider/package/solaris.rb
+++ b/lib/chef/provider/package/solaris.rb
@@ -1,6 +1,6 @@
#
# Author:: Toomas Pelberg (<toomasp@gmx.net>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,10 +15,9 @@
# 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_relative "../package"
+require_relative "../../resource/package"
+require_relative "../../mixin/get_source_from_package"
class Chef
class Provider
@@ -27,51 +26,49 @@ class Chef
include Chef::Mixin::GetSourceFromPackage
- provides :package, platform: "nexentacore"
- provides :package, platform: "solaris2", platform_version: "< 5.11"
- provides :solaris_package, os: "solaris2"
+ provides :solaris_package
# def initialize(*args)
# super
- # @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ # @current_resource = Chef::Resource::Package.new(new_resource.name)
# end
def define_resource_requirements
super
requirements.assert(:install) do |a|
- a.assertion { @new_resource.source }
- a.failure_message Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install"
+ a.assertion { new_resource.source }
+ a.failure_message Chef::Exceptions::Package, "Source for package #{new_resource.package_name} required for action install"
end
requirements.assert(:all_actions) do |a|
- a.assertion { !@new_resource.source || @package_source_found }
- a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
- a.whyrun "would assume #{@new_resource.source} would be have previously been made available"
+ a.assertion { !new_resource.source || @package_source_found }
+ a.failure_message Chef::Exceptions::Package, "Package #{new_resource.package_name} not found: #{new_resource.source}"
+ a.whyrun "would assume #{new_resource.source} would be have previously been made available"
end
end
def load_current_resource
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
+ @current_resource = Chef::Resource::Package.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
- if @new_resource.source
- @package_source_found = ::File.exists?(@new_resource.source)
+ if new_resource.source
+ @package_source_found = ::File.exist?(new_resource.source)
if @package_source_found
- Chef::Log.debug("#{@new_resource} checking pkg status")
- shell_out_with_timeout("pkginfo -l -d #{@new_resource.source} #{@new_resource.package_name}").stdout.each_line do |line|
+ logger.trace("#{new_resource} checking pkg status")
+ shell_out("pkginfo", "-l", "-d", new_resource.source, new_resource.package_name).stdout.each_line do |line|
case line
when /VERSION:\s+(.+)/
- @new_resource.version($1)
+ new_resource.version($1)
end
end
end
end
- Chef::Log.debug("#{@new_resource} checking install state")
- status = shell_out_with_timeout("pkginfo -l #{@current_resource.package_name}")
+ logger.trace("#{new_resource} checking install state")
+ status = shell_out("pkginfo", "-l", current_resource.package_name)
status.stdout.each_line do |line|
case line
when /VERSION:\s+(.+)/
- Chef::Log.debug("#{@new_resource} version #{$1} is already installed")
- @current_resource.version($1)
+ logger.trace("#{new_resource} version #{$1} is already installed")
+ current_resource.version($1)
end
end
@@ -79,56 +76,58 @@ class Chef
raise Chef::Exceptions::Package, "pkginfo failed - #{status.inspect}!"
end
- @current_resource
+ current_resource
end
def candidate_version
return @candidate_version if @candidate_version
- status = shell_out_with_timeout("pkginfo -l -d #{@new_resource.source} #{new_resource.package_name}")
+
+ status = shell_out("pkginfo", "-l", "-d", new_resource.source, new_resource.package_name)
status.stdout.each_line do |line|
case line
when /VERSION:\s+(.+)/
@candidate_version = $1
- @new_resource.version($1)
- Chef::Log.debug("#{@new_resource} setting install candidate version to #{@candidate_version}")
+ new_resource.version($1)
+ logger.trace("#{new_resource} setting install candidate version to #{@candidate_version}")
end
end
unless status.exitstatus == 0
- raise Chef::Exceptions::Package, "pkginfo -l -d #{@new_resource.source} - #{status.inspect}!"
+ raise Chef::Exceptions::Package, "pkginfo -l -d #{new_resource.source} - #{status.inspect}!"
end
+
@candidate_version
end
def install_package(name, version)
- Chef::Log.debug("#{@new_resource} package install options: #{@new_resource.options}")
- if @new_resource.options.nil?
- if ::File.directory?(@new_resource.source) # CHEF-4469
- command = "pkgadd -n -d #{@new_resource.source} #{@new_resource.package_name}"
- else
- command = "pkgadd -n -d #{@new_resource.source} all"
- end
- shell_out_with_timeout!(command)
- Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
+ logger.trace("#{new_resource} package install options: #{options}")
+ if options.nil?
+ command = if ::File.directory?(new_resource.source) # CHEF-4469
+ [ "pkgadd", "-n", "-d", new_resource.source, new_resource.package_name ]
+ else
+ [ "pkgadd", "-n", "-d", new_resource.source, "all" ]
+ end
+ shell_out!(command)
+ logger.trace("#{new_resource} installed version #{new_resource.version} from: #{new_resource.source}")
else
- if ::File.directory?(@new_resource.source) # CHEF-4469
- command = "pkgadd -n#{expand_options(@new_resource.options)} -d #{@new_resource.source} #{@new_resource.package_name}"
- else
- command = "pkgadd -n#{expand_options(@new_resource.options)} -d #{@new_resource.source} all"
- end
- shell_out_with_timeout!(command)
- Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
+ command = if ::File.directory?(new_resource.source) # CHEF-4469
+ [ "pkgadd", "-n", options, "-d", new_resource.source, new_resource.package_name ]
+ else
+ [ "pkgadd", "-n", options, "-d", new_resource.source, "all" ]
+ end
+ shell_out!(*command)
+ logger.trace("#{new_resource} installed version #{new_resource.version} from: #{new_resource.source}")
end
end
- alias_method :upgrade_package, :install_package
+ alias upgrade_package install_package
def remove_package(name, version)
- if @new_resource.options.nil?
- shell_out_with_timeout!( "pkgrm -n #{name}" )
- Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
+ if options.nil?
+ shell_out!( "pkgrm", "-n", name )
+ logger.trace("#{new_resource} removed version #{new_resource.version}")
else
- shell_out_with_timeout!( "pkgrm -n#{expand_options(@new_resource.options)} #{name}" )
- Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
+ shell_out!( "pkgrm", "-n", options, name )
+ logger.trace("#{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 753d3c279e..c722d8222c 100644
--- a/lib/chef/provider/package/windows.rb
+++ b/lib/chef/provider/package/windows.rb
@@ -1,6 +1,6 @@
#
# Author:: Bryan McLellan <btm@loftninjas.org>
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,11 +16,12 @@
# limitations under the License.
#
-require "chef/mixin/uris"
-require "chef/resource/windows_package"
-require "chef/provider/package"
-require "chef/util/path_helper"
-require "chef/mixin/checksum"
+require_relative "../../mixin/uris"
+require_relative "../../resource/windows_package"
+require_relative "../package"
+require_relative "../../util/path_helper"
+require_relative "../../mixin/checksum"
+autoload :CGI, "cgi"
class Chef
class Provider
@@ -30,28 +31,41 @@ class Chef
include Chef::Mixin::Checksum
provides :package, os: "windows"
- provides :windows_package, os: "windows"
+ provides :windows_package
- require "chef/provider/package/windows/registry_uninstall_entry.rb"
+ autoload :RegistryUninstallEntry, ::File.expand_path("windows/registry_uninstall_entry.rb", __dir__)
def define_resource_requirements
+ if new_resource.checksum
+ requirements.assert(:install) do |a|
+ a.assertion { new_resource.checksum == checksum(source_location) }
+ a.failure_message Chef::Exceptions::Package, "Checksum on resource (#{short_cksum(new_resource.checksum)}) does not match checksum on content (#{short_cksum(source_location)})"
+ end
+ end
+
requirements.assert(:install) do |a|
a.assertion { new_resource.source || 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."
+ a.failure_message Chef::Exceptions::NoWindowsPackageSource, "Source for package #{new_resource.package_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
+
+ unless uri_scheme?(new_resource.source)
+ requirements.assert(:install) do |a|
+ a.assertion { ::File.exist?(new_resource.source) }
+ a.failure_message Chef::Exceptions::Package, "Source for package #{new_resource.package_name} does not exist"
+ a.whyrun "Assuming source file #{new_resource.source} would have been created."
+ end
end
end
- # load_current_resource is run in Chef::Provider#run_action when not in whyrun_mode?
def load_current_resource
- @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
+ if uri_scheme?(new_resource.source) && action == :install
+ download_source_file
end
+ @current_resource = Chef::Resource::WindowsPackage.new(new_resource.name)
+ current_resource.version(package_provider.installed_version)
+ new_resource.version(package_provider.package_version) if package_provider.package_version
+
current_resource
end
@@ -59,12 +73,12 @@ class Chef
@package_provider ||= begin
case installer_type
when :msi
- Chef::Log.debug("#{new_resource} is MSI")
- require "chef/provider/package/windows/msi"
+ logger.trace("#{new_resource} is MSI")
+ require_relative "windows/msi"
Chef::Provider::Package::Windows::MSI.new(resource_for_provider, uninstall_registry_entries)
else
- Chef::Log.debug("#{new_resource} is EXE with type '#{installer_type}'")
- require "chef/provider/package/windows/exe"
+ logger.trace("#{new_resource} is EXE with type '#{installer_type}'")
+ require_relative "windows/exe"
Chef::Provider::Package::Windows::Exe.new(resource_for_provider, installer_type, uninstall_registry_entries)
end
end
@@ -104,8 +118,8 @@ class Chef
return :nsis
end
- if io.tell() < filesize
- io.seek(io.tell() - overlap)
+ if io.tell < filesize
+ io.seek(io.tell - overlap)
end
end
@@ -113,24 +127,13 @@ class Chef
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}'"
+ raise Chef::Exceptions::CannotDetermineWindowsInstallerType, "Installer type for Windows Package '#{new_resource.package_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)
@@ -156,6 +159,18 @@ class Chef
# 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
+ #
+ # FIXME: this breaks the semantics of the superclass and needs to get unwound. Since these package
+ # providers don't support multipackage they can't put multiple versions into this array. The windows
+ # package managers need this in order to uninstall multiple installed version, and they should track
+ # that in something like an `uninstall_version_array` of their own. The superclass does not implement
+ # this kind of feature. Doing this here breaks LSP and will create bugs since the superclass will not
+ # expect it at all. The `current_resource.version` also MUST NOT be an array if the package provider
+ # is not multipackage. The existing implementation of package_provider.installed_version should probably
+ # be what `uninstall_version_array` is, and then that list should be sorted and last/first'd into the
+ # current_resource.version. The current_version_array method was not intended to be overwritten by
+ # subclasses (but ruby provides no feature to block doing so -- it is already marked as private).
+ #
def current_version_array
[ current_resource.version ]
end
@@ -165,7 +180,11 @@ class Chef
#
# @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")
+ version_equals?(current_version, new_version)
+ end
+
+ def version_equals?(current_version, new_version)
+ logger.trace("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
@@ -179,6 +198,17 @@ class Chef
private
+ def version_compare(v1, v2)
+ if v1 == "latest" || v2 == "latest"
+ return 0
+ end
+
+ gem_v1 = Gem::Version.new(v1)
+ gem_v2 = Gem::Version.new(v2)
+
+ gem_v1 <=> gem_v2
+ end
+
def uninstall_registry_entries
@uninstall_registry_entries ||= Chef::Provider::Package::Windows::RegistryUninstallEntry.find_entries(new_resource.package_name)
end
@@ -195,10 +225,11 @@ class Chef
end
def downloadable_file_missing?
- !new_resource.source.nil? && uri_scheme?(new_resource.source) && !::File.exists?(source_location)
+ !new_resource.source.nil? && uri_scheme?(new_resource.source) && !::File.exist?(source_location)
end
def resource_for_provider
+ # XXX: this is crazy
@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.cookbook_name = new_resource.cookbook_name
@@ -206,24 +237,29 @@ class Chef
r.timeout(new_resource.timeout)
r.returns(new_resource.returns)
r.options(new_resource.options)
+ r.sensitive(new_resource.sensitive)
end
end
def download_source_file
source_resource.run_action(:create)
- Chef::Log.debug("#{new_resource} fetched source file to #{source_resource.path}")
+ logger.trace("#{new_resource} fetched source file to #{default_download_cache_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.cookbook_name = new_resource.cookbook_name
- r.checksum(new_resource.checksum)
- r.backup(false)
-
+ # It seems correct that this is a build_resource rather than declare_resource/DSL use since updating should not trigger a notification
+ # unless the downloaded file is actually installed. The case where the remote_file downloads the package but the package is already
+ # installed on the target should not trigger a notification since the running state did not change.
+ @source_resource ||= build_resource(:remote_file, default_download_cache_path) do
+ source(new_resource.source)
+ cookbook_name = new_resource.cookbook_name
+ checksum(new_resource.checksum)
+ backup(false)
+
+ # since the source_resource can mutate here, we save off the @source_resource so we can inspect the actual state later
if new_resource.remote_file_attributes
new_resource.remote_file_attributes.each do |(k, v)|
- r.send(k.to_sym, v)
+ send(k.to_sym, v)
end
end
end
@@ -231,7 +267,7 @@ class Chef
def default_download_cache_path
uri = ::URI.parse(new_resource.source)
- filename = ::File.basename(::URI.unescape(uri.path))
+ filename = ::File.basename(::CGI.unescape(uri.path))
file_cache_dir = Chef::FileCache.create_cache_path("package/")
Chef::Util::PathHelper.cleanpath("#{file_cache_dir}/#{filename}")
end
@@ -247,22 +283,13 @@ class Chef
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
-
def msi?
return true if new_resource.installer_type == :msi
if source_location.nil?
inferred_registry_type == :msi
else
- ::File.extname(source_location).casecmp(".msi").zero?
+ ::File.extname(source_location).casecmp(".msi") == 0
end
end
end
diff --git a/lib/chef/provider/package/windows/exe.rb b/lib/chef/provider/package/windows/exe.rb
index 44a2f19d1e..191e8fb4ae 100644
--- a/lib/chef/provider/package/windows/exe.rb
+++ b/lib/chef/provider/package/windows/exe.rb
@@ -1,7 +1,7 @@
#
# Author:: Seth Chisamore (<schisamo@chef.io>)
# Author:: Matt Wrock <matt@mattwrock.com>
-# Copyright:: Copyright 2011-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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/mixin/shell_out"
+require_relative "../../../mixin/shell_out"
class Chef
class Provider
@@ -28,11 +28,13 @@ class Chef
def initialize(resource, installer_type, uninstall_entries)
@new_resource = resource
+ @logger = new_resource.logger
@installer_type = installer_type
@uninstall_entries = uninstall_entries
end
attr_reader :new_resource
+ attr_reader :logger
attr_reader :installer_type
attr_reader :uninstall_entries
@@ -43,7 +45,7 @@ class Chef
# 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")
+ logger.trace("#{new_resource} checking package version")
current_installed_version
end
@@ -52,7 +54,7 @@ class Chef
end
def install_package
- Chef::Log.debug("#{new_resource} installing #{new_resource.installer_type} package '#{new_resource.source}'")
+ logger.trace("#{new_resource} installing #{new_resource.installer_type} package '#{new_resource.source}'")
shell_out!(
[
"start",
@@ -62,16 +64,16 @@ class Chef
unattended_flags,
expand_options(new_resource.options),
"& exit %%%%ERRORLEVEL%%%%",
- ].join(" "), timeout: new_resource.timeout, returns: new_resource.returns
+ ].join(" "), default_env: false, timeout: new_resource.timeout, returns: new_resource.returns, sensitive: new_resource.sensitive
)
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), { :timeout => new_resource.timeout, :returns => new_resource.returns })
+ .map(&:uninstall_string).uniq.each do |uninstall_string|
+ logger.trace("Registry provided uninstall string for #{new_resource} is '#{uninstall_string}'")
+ shell_out!(uninstall_command(uninstall_string), default_env: false, timeout: new_resource.timeout, returns: new_resource.returns)
end
end
@@ -85,13 +87,14 @@ class Chef
" ",
unattended_flags,
].join
- %Q{start "" /wait #{uninstall_string} & exit %%%%ERRORLEVEL%%%%}
+ %{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
+ @current_installed_version ||=
+ if uninstall_entries.count != 0
+ uninstall_entries.map(&:display_version).uniq
+ end
end
# http://unattended.sourceforge.net/installers.php
diff --git a/lib/chef/provider/package/windows/msi.rb b/lib/chef/provider/package/windows/msi.rb
index ac771688e7..1ce8add80d 100644
--- a/lib/chef/provider/package/windows/msi.rb
+++ b/lib/chef/provider/package/windows/msi.rb
@@ -1,6 +1,6 @@
#
# Author:: Bryan McLellan <btm@loftninjas.org>
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,25 +16,27 @@
# limitations under the License.
#
-# TODO: Allow @new_resource.source to be a Product Code as a GUID for uninstall / network install
+# 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/) && Chef::Platform.supports_msi?
-require "chef/mixin/shell_out"
+require_relative "../../../win32/api/installer" if RUBY_PLATFORM.match?(/mswin|mingw32|windows/)
+require_relative "../../../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/) && Chef::Platform.supports_msi?
+ include Chef::ReservedNames::Win32::API::Installer if RUBY_PLATFORM.match?(/mswin|mingw32|windows/)
include Chef::Mixin::ShellOut
def initialize(resource, uninstall_entries)
@new_resource = resource
+ @logger = new_resource.logger
@uninstall_entries = uninstall_entries
end
attr_reader :new_resource
+ attr_reader :logger
attr_reader :uninstall_entries
# From Chef::Provider::Package
@@ -45,45 +47,47 @@ class Chef
# Returns a version if the package is installed or nil if it is not.
def installed_version
if !new_resource.source.nil? && ::File.exist?(new_resource.source)
- Chef::Log.debug("#{new_resource} getting product code for package at #{new_resource.source}")
+ logger.trace("#{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}")
+ logger.trace("#{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
+ if uninstall_entries.count != 0
+ uninstall_entries.map(&:display_version).uniq
end
end
end
def package_version
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}")
+ logger.trace("#{new_resource} getting product version for package at #{new_resource.source}")
get_product_property(new_resource.source, "ProductVersion")
end
end
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 })
+ logger.trace("#{new_resource} installing MSI package '#{new_resource.source}'")
+ shell_out!("msiexec /qn /i \"#{new_resource.source}\" #{expand_options(new_resource.options)}", default_env: false, timeout: new_resource.timeout, returns: new_resource.returns)
end
def remove_package
# We could use MsiConfigureProduct here, but we'll start off with msiexec
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 })
+ logger.trace("#{new_resource} removing MSI package '#{new_resource.source}'")
+ shell_out!("msiexec /qn /x \"#{new_resource.source}\" #{expand_options(new_resource.options)}", default_env: false, 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
+ .map(&:uninstall_string).uniq.each do |uninstall_string|
+ uninstall_string = "msiexec /x #{uninstall_string.match(/{.*}/)}"
+ uninstall_string += expand_options(new_resource.options)
+ uninstall_string += " /q" unless %r{ /q}.match?(uninstall_string.downcase)
+ logger.trace("#{new_resource} removing MSI package version using '#{uninstall_string}'")
+ shell_out!(uninstall_string, default_env: false, timeout: new_resource.timeout, returns: new_resource.returns)
+ 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
index a693558883..6e7b825256 100644
--- a/lib/chef/provider/package/windows/registry_uninstall_entry.rb
+++ b/lib/chef/provider/package/windows/registry_uninstall_entry.rb
@@ -1,7 +1,7 @@
#
# Author:: Seth Chisamore (<schisamo@chef.io>)
# Author:: Matt Wrock <matt@mattwrock.com>
-# Copyright:: Copyright 2011-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,7 +17,9 @@
# limitations under the License.
#
-require "win32/registry" if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+module Win32
+ autoload :Registry, File.expand_path("../../../monkey_patches/win32/registry", __dir__) if RUBY_PLATFORM.match?(/mswin|mingw32|windows/)
+end
class Chef
class Provider
@@ -26,7 +28,7 @@ class Chef
class RegistryUninstallEntry
def self.find_entries(package_name)
- Chef::Log.debug("Finding uninstall entries for #{package_name}")
+ logger.trace("Finding uninstall entries for #{package_name}")
entries = []
[
[::Win32::Registry::HKEY_LOCAL_MACHINE, (::Win32::Registry::Constants::KEY_READ | 0x0100)],
@@ -37,39 +39,51 @@ class Chef
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}")
+
+ entry = reg.open(key, desired)
+ display_name = read_registry_property(entry, "DisplayName")
+ if display_name.to_s.rstrip == package_name
+ quiet_uninstall_string = RegistryUninstallEntry.read_registry_property(entry, "QuietUninstallString")
+ entries.push(quiet_uninstall_string_key?(quiet_uninstall_string, hkey, key, entry))
end
+ rescue ::Win32::Registry::Error => ex
+ logger.trace("Registry error opening key '#{key}' on node #{desired}: #{ex}")
+
end
end
rescue ::Win32::Registry::Error => ex
- Chef::Log.debug("Registry error opening hive '#{hkey[0]}' :: #{desired}: #{ex}")
+ logger.trace("Registry error opening hive '#{hkey[0]}' :: #{desired}: #{ex}")
end
end
entries
end
+ def self.quiet_uninstall_string_key?(quiet_uninstall_string, hkey, key, entry)
+ return RegistryUninstallEntry.new(hkey, key, entry) if quiet_uninstall_string.nil?
+
+ RegistryUninstallEntry.new(hkey, key, entry, "QuietUninstallString")
+ end
+
def self.read_registry_property(data, property)
data[property]
- rescue ::Win32::Registry::Error => ex
- Chef::Log.debug("Failure to read property '#{property}'")
+ rescue ::Win32::Registry::Error
+ logger.trace("Failure to read property '#{property}'")
nil
end
- def initialize(hive, key, registry_data)
- Chef::Log.debug("Creating uninstall entry for #{hive}::#{key}")
+ def self.logger
+ Chef::Log
+ end
+
+ def initialize(hive, key, registry_data, uninstall_key = "UninstallString")
+ @logger = Chef::Log.with_child({ subsystem: "registry_uninstall_entry" })
+ logger.trace("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")
+ @uninstall_string = RegistryUninstallEntry.read_registry_property(registry_data, uninstall_key)
end
attr_reader :hive
@@ -78,6 +92,7 @@ class Chef
attr_reader :display_version
attr_reader :uninstall_string
attr_reader :data
+ attr_reader :logger
UNINSTALL_SUBKEY = 'Software\Microsoft\Windows\CurrentVersion\Uninstall'.freeze
end
diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb
index 74d52946f7..76b9b15172 100644
--- a/lib/chef/provider/package/yum.rb
+++ b/lib/chef/provider/package/yum.rb
@@ -1,6 +1,5 @@
-
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+#
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,447 +15,264 @@
# limitations under the License.
#
-require "chef/config"
-require "chef/provider/package"
-require "chef/resource/yum_package"
-require "chef/mixin/get_source_from_package"
-require "chef/provider/package/yum/rpm_utils"
-require "chef/provider/package/yum/yum_cache"
+require_relative "../package"
+require_relative "../../resource/yum_package"
+require_relative "../../mixin/which"
+require_relative "../../mixin/shell_out"
+require_relative "../../mixin/get_source_from_package"
+require_relative "yum/python_helper"
+require_relative "yum/version"
+# the stubs in the YumCache class are still an external API
+require_relative "yum/yum_cache"
class Chef
class Provider
class Package
class Yum < Chef::Provider::Package
+ extend Chef::Mixin::Which
+ extend Chef::Mixin::ShellOut
+ include Chef::Mixin::GetSourceFromPackage
- provides :package, platform_family: %w{rhel fedora}
- provides :yum_package, os: "linux"
+ allow_nils
+ use_multipackage_api
+ use_package_name_for_source
- include Chef::Mixin::GetSourceFromPackage
+ provides :package, platform_family: "fedora_derived"
- def initialize(new_resource, run_context)
- super
+ provides :yum_package
- @yum = YumCache.instance
- @yum.yum_binary = yum_binary
+ #
+ # Most of the magic in this class happens in the python helper script. The ruby side of this
+ # provider knows only enough to translate Chef-style new_resource name+package+version into
+ # a request to the python side. The python side is then responsible for knowing everything
+ # about RPMs and what is installed and what is available. The ruby side of this class should
+ # remain a lightweight translation layer to translate Chef requests into RPC requests to
+ # python. This class knows nothing about how to compare RPM versions, and does not maintain
+ # any cached state of installed/available versions and should be kept that way.
+ #
+ def python_helper
+ @python_helper ||= PythonHelper.instance
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
+ def load_current_resource
+ flushcache if new_resource.flush_cache[:before]
- # Extra attributes
- #
+ @current_resource = Chef::Resource::YumPackage.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
+ current_resource.version(get_current_versions)
- 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
+ current_resource
end
- def arch
- if @new_resource.respond_to?("arch")
- @new_resource.arch
- else
- nil
+ def define_resource_requirements
+ requirements.assert(:install, :upgrade, :remove, :purge) do |a|
+ a.assertion { !new_resource.source || ::File.exist?(new_resource.source) }
+ a.failure_message Chef::Exceptions::Package, "Package #{new_resource.package_name} not found: #{new_resource.source}"
+ a.whyrun "assuming #{new_resource.source} would have previously been created"
end
+
+ super
end
- def set_arch(arch)
- if @new_resource.respond_to?("arch")
- @new_resource.arch(arch)
+ def candidate_version
+ package_name_array.each_with_index.map do |pkg, i|
+ available_version(i).version_with_arch
end
end
- def flush_cache
- if @new_resource.respond_to?("flush_cache")
- @new_resource.flush_cache
- else
- { :before => false, :after => false }
+ def get_current_versions
+ package_name_array.each_with_index.map do |pkg, i|
+ installed_version(i).version_with_arch
end
end
- # Helpers
- #
+ def install_package(names, versions)
+ method = nil
+ methods = []
+ names.each_with_index do |n, i|
+ next if n.nil?
- def yum_arch(arch)
- arch ? ".#{arch}" : nil
- end
+ av = available_version(i)
+ name = av.name # resolve the name via the available/candidate version
+ iv = python_helper.package_query(:whatinstalled, av.name_with_arch, options: options)
- def yum_command(command)
- command = "#{yum_binary} #{command}"
- Chef::Log.debug("#{@new_resource}: yum command: \"#{command}\"")
- status = shell_out_with_timeout(command, { :timeout => Chef::Config[:yum_timeout] })
-
- # 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
- # cause yum to emit a non fatal warning but still exit(1). As there's currently no
- # way to suppress this behavior and an exit(1) will break a Chef run we make an
- # effort to trap these and re-run the same install command - it will either fail a
- # second time or succeed.
- #
- # A cleaner solution would have to be done in python and better hook into
- # yum/rpm to handle exceptions as we see fit.
- if status.exitstatus == 1
- status.stdout.each_line do |l|
- # rpm-4.4.2.3 lib/psm.c line 2182
- 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_with_timeout(command, { :timeout => Chef::Config[:yum_timeout] })
- break
- end
+ method = "install"
+
+ # If this is a package like the kernel that can be installed multiple times, we'll skip over this logic
+ if new_resource.allow_downgrade && version_gt?(iv.version_with_arch, av.version_with_arch) && !python_helper.install_only_packages(name)
+ # We allow downgrading only in the event of single-package
+ # rules where the user explicitly allowed it
+ method = "downgrade"
end
- end
- if status.exitstatus > 0
- command_output = "STDOUT: #{status.stdout}\nSTDERR: #{status.stderr}"
- raise Chef::Exceptions::Exec, "#{command} returned #{status.exitstatus}:\n#{command_output}"
+ methods << method
end
- end
-
- # Standard Provider methods for Parent
- #
- def load_current_resource
- if flush_cache[:before]
- @yum.reload
+ # We could split this up into two commands if we wanted to, but
+ # for now, just don't support this.
+ if methods.uniq.length > 1
+ raise Chef::Exceptions::Package, "Multipackage rule has a mix of upgrade and downgrade packages. Cannot proceed."
end
- if @new_resource.options
- repo_control = []
- @new_resource.options.split.each do |opt|
- if opt =~ %r{--(enable|disable)repo=.+}
- repo_control << opt
- end
- end
-
- if repo_control.size > 0
- @yum.enable_extra_repo_control(repo_control.join(" "))
- else
- @yum.disable_extra_repo_control
- end
+ if new_resource.source
+ yum(options, "-y", method, new_resource.source)
else
- @yum.disable_extra_repo_control
+ resolved_names = names.each_with_index.map { |name, i| available_version(i).to_s unless name.nil? }
+ yum(options, "-y", method, resolved_names)
end
+ flushcache
+ end
- # At this point package_name could be:
- #
- # 1) a package name, eg: "foo"
- # 2) a package name.arch, eg: "foo.i386"
- # 3) or a dependency, eg: "foo >= 1.1"
-
- # Check if we have name or name+arch which has a priority over a dependency
- package_name_array.each_with_index do |n, index|
- unless @yum.package_available?(n)
- # If they aren't in the installed packages they could be a dependency
- dep = parse_dependency(n, new_version_array[index])
- if dep
- if @new_resource.package_name.is_a?(Array)
- @new_resource.package_name(package_name_array - [n] + [dep.first])
- @new_resource.version(new_version_array - [new_version_array[index]] + [dep.last]) if dep.last
- else
- @new_resource.package_name(dep.first)
- @new_resource.version(dep.last) if dep.last
- end
- end
- end
- end
+ # yum upgrade does not work on uninstalled packaged, while install will upgrade
+ alias upgrade_package install_package
- @current_resource = Chef::Resource::YumPackage.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
+ def remove_package(names, versions)
+ resolved_names = names.each_with_index.map { |name, i| installed_version(i).to_s unless name.nil? }
+ yum(options, "-y", "remove", resolved_names)
+ flushcache
+ end
- 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
+ alias purge_package remove_package
- Chef::Log.debug("#{@new_resource} checking rpm status")
- shell_out_with_timeout!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}", :timeout => Chef::Config[:yum_timeout]).stdout.each_line do |line|
- case line
- when /([\w\d_.-]+)\s([\w\d_.-]+)/
- @current_resource.package_name($1)
- @new_resource.version($2)
- end
- end
- @candidate_version << @new_resource.version
- installed_version << @yum.installed_version(@current_resource.package_name, arch)
- else
+ action :flush_cache do
+ flushcache
+ end
- 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
+ # NB: the yum_package provider manages individual single packages, please do not submit issues or PRs to try to add wildcard
+ # support to lock / unlock. The best solution is to write an execute resource which does a not_if `yum versionlock | grep '^pattern`` kind of approach
+ def lock_package(names, versions)
+ yum("-d0", "-e0", "-y", options, "versionlock", "add", resolved_package_lock_names(names))
+ end
+
+ # NB: the yum_package provider manages individual single packages, please do not submit issues or PRs to try to add wildcard
+ # support to lock / unlock. The best solution is to write an execute resource which does a only_if `yum versionlock | grep '^pattern`` kind of approach
+ def unlock_package(names, versions)
+ # yum versionlock delete on rhel6 needs the glob nonsense in the following command
+ yum("-d0", "-e0", "-y", options, "versionlock", "delete", resolved_package_lock_names(names).map { |n| "*:#{n}-*" })
+ end
- if @new_resource.version
- new_resource =
- "#{@new_resource.package_name}-#{@new_resource.version}#{yum_arch(parch)}"
+ private
+
+ # this will resolve things like `/usr/bin/perl` or virtual packages like `mysql` -- it will not work (well? at all?) with globs that match multiple packages
+ def resolved_package_lock_names(names)
+ names.each_with_index.map do |name, i|
+ unless name.nil?
+ if installed_version(i).version.nil?
+ available_version(i).name
else
- new_resource = "#{@new_resource.package_name}#{yum_arch(parch)}"
+ installed_version(i).name
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
-
- Chef::Log.debug("#{@new_resource} installed version: #{installed_version || "(none)"} candidate version: " +
- "#{@candidate_version || "(none)"}")
-
- @current_resource
end
- def install_remote_package(name, version)
- # 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_for_name(n))
- end
- method = log_method = nil
- methods = []
- if all_avail
- # More Yum fun:
- #
- # yum install of an old name+version will exit(1)
- # yum install of an old name+version+arch will exit(0) for some reason
- #
- # Some packages can be installed multiple times like the kernel
- as_array(name).zip(as_array(version)).each do |n, v|
- method = "install"
- log_method = "installing"
- idx = package_name_array.index(n)
- unless @yum.allow_multi_install.include?(n)
- if RPMVersion.parse(current_version_array[idx]) > RPMVersion.parse(v)
- # We allow downgrading only in the evenit of single-package
- # rules where the user explicitly allowed it
- if allow_downgrade
- method = "downgrade"
- log_method = "downgrading"
- else
- # we bail like yum when the package is older
- raise Chef::Exceptions::Package, "Installed package #{n}-#{current_version_array[idx]} is newer " +
- "than candidate package #{n}-#{v}"
- end
- end
+ def locked_packages
+ @locked_packages ||=
+ begin
+ locked = yum("versionlock", "list")
+ locked.stdout.each_line.map do |line|
+ line.sub(/-[^-]*-[^-]*$/, "").split(":").last.strip
end
- # methods don't count for packages we won't be touching
- next if RPMVersion.parse(current_version_array[idx]) == RPMVersion.parse(v)
- methods << method
end
+ end
- # We could split this up into two commands if we wanted to, but
- # for now, just don't support this.
- if methods.uniq.length > 1
- raise Chef::Exceptions::Package, "Multipackage rule #{name} has a mix of upgrade and downgrade packages. Cannot proceed."
- end
+ def packages_all_locked?(names, versions)
+ resolved_package_lock_names(names).all? { |n| locked_packages.include? n }
+ end
- repos = []
- pkg_string_bits = []
- as_array(name).zip(as_array(version)).each do |n, v|
- idx = package_name_array.index(n)
- a = arch_for_name(n)
- s = ""
- unless v == current_version_array[idx]
- s = "#{n}-#{v}#{yum_arch(a)}"
- repo = @yum.package_repository(n, v, a)
- repos << "#{s} from #{repo} repository"
- pkg_string_bits << s
- end
- end
- pkg_string = pkg_string_bits.join(" ")
- Chef::Log.info("#{@new_resource} #{log_method} #{repos.join(' ')}")
- 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)"
- end
+ def packages_all_unlocked?(names, versions)
+ !resolved_package_lock_names(names).any? { |n| locked_packages.include? n }
end
- def install_package(name, version)
- if @new_resource.source
- yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} localinstall #{@new_resource.source}")
- else
- install_remote_package(name, version)
- end
+ def version_gt?(v1, v2)
+ return false if v1.nil? || v2.nil?
- if flush_cache[:after]
- @yum.reload
- else
- @yum.reload_installed
- end
+ python_helper.compare_versions(v1, v2) == 1
end
- # Keep upgrades from trying to install an older candidate version. Can happen when a new
- # version is installed then removed from a repository, now the older available version
- # shows up as a viable install candidate.
- #
- # Can be done in upgrade_package but an upgraded from->to log message slips out
- #
- # Hacky - better overall solution? Custom compare in Package provider?
- def action_upgrade
- # Could be uninstalled or have no candidate
- if @current_resource.version.nil? || !candidate_version_array.any?
- super
- elsif candidate_version_array.zip(current_version_array).any? do |c, i|
- RPMVersion.parse(c) > RPMVersion.parse(i)
- end
- super
- else
- Chef::Log.debug("#{@new_resource} is at the latest version - nothing to do")
- end
+ def version_equals?(v1, v2)
+ return false if v1.nil? || v2.nil?
+
+ python_helper.compare_versions(v1, v2) == 0
end
- def upgrade_package(name, version)
- install_package(name, version)
+ def version_compare(v1, v2)
+ return false if v1.nil? || v2.nil?
+
+ python_helper.compare_versions(v1, v2)
end
- def remove_package(name, version)
- if version
- 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 do |n|
- a = arch_for_name(n)
- "#{n}#{yum_arch(a)}"
- end.join(" ")
+ def resolve_source_to_version_obj
+ shell_out!("rpm -qp --queryformat '%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n' #{new_resource.source}").stdout.each_line do |line|
+ # this is another case of committing the sin of doing some lightweight mangling of RPM versions in ruby -- but the output of the rpm command
+ # does not match what the yum library accepts.
+ case line
+ when /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$/
+ return Version.new($1, "#{$2 == "(none)" ? "0" : $2}:#{$3}-#{$4}", $5)
+ end
end
- yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} remove #{remove_str}")
+ end
- if flush_cache[:after]
- @yum.reload
- else
- @yum.reload_installed
- end
+ # @return Array<Version>
+ def available_version(index)
+ @available_version ||= []
+
+ @available_version[index] ||= if new_resource.source
+ resolve_source_to_version_obj
+ else
+ python_helper.package_query(:whatavailable, package_name_array[index], version: safe_version_array[index], arch: safe_arch_array[index], options: options)
+ end
+
+ @available_version[index]
end
- def purge_package(name, version)
- remove_package(name, version)
+ # @return Array<Version>
+ def installed_version(index)
+ @installed_version ||= []
+ @installed_version[index] ||= if new_resource.source
+ python_helper.package_query(:whatinstalled, available_version(index).name, arch: safe_arch_array[index], options: options)
+ else
+ python_helper.package_query(:whatinstalled, package_name_array[index], arch: safe_arch_array[index], options: options)
+ end
+ @installed_version[index]
end
- private
+ # cache flushing is accomplished by simply restarting the python helper. this produces a roughly
+ # 15% hit to the runtime of installing/removing/upgrading packages. correctly using multipackage
+ # array installs (and the multipackage cookbook) can produce 600% improvements in runtime.
+ def flushcache
+ python_helper.restart
+ end
- def parse_arch(package_name)
- # Allow for foo.x86_64 style package_name like yum uses in it's output
- #
- 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.
- 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? && old_candidate.nil?) && (new_installed || new_candidate)
- Chef::Log.debug("Parsed out arch #{new_arch}, new package name is #{new_package_name}")
- return new_package_name, new_arch
+ 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
- return package_name, nil
end
- # If we don't have the package we could have been passed a 'whatprovides' feature
- #
- # eg: yum install "perl(Config)"
- # yum install "mtr = 2:0.71-3.1"
- # yum install "mtr > 2:0.71"
- #
- # We support resolving these out of the Provides data imported from yum-dump.py and
- # matching them up with an actual package so the standard resource handling can apply.
- #
- # There is currently no support for filename matching.
- def parse_dependency(name, version)
- # Transform the package_name into a requirement
-
- # If we are passed a version or a version constraint we have to assume it's a requirement first. If it can't be
- # parsed only yum_require.name will be set and @new_resource.version will be left intact
- if version
- require_string = "#{name} #{version}"
+ def yum(*args)
+ shell_out!(yum_binary, *args)
+ end
+
+ def safe_version_array
+ if new_resource.version.is_a?(Array)
+ new_resource.version
+ elsif new_resource.version.nil?
+ package_name_array.map { nil }
else
- # Transform the package_name into a requirement, might contain a version, could just be
- # a match for virtual provides
- require_string = name
+ [ new_resource.version ]
end
- yum_require = RPMRequire.parse(require_string)
- # and gather all the packages that have a Provides feature satisfying the requirement.
- # It could be multiple be we can only manage one
- packages = @yum.packages_from_require(yum_require)
-
- if packages.empty?
- # Don't bother if we are just ensuring a package is removed - we don't need Provides data
- actions = Array(@new_resource.action)
- unless actions.size == 1 && (actions[0] == :remove || actions[0] == :purge)
- Chef::Log.debug("#{@new_resource} couldn't match #{@new_resource.package_name} in " +
- "installed Provides, loading available Provides - this may take a moment")
- @yum.reload_provides
- packages = @yum.packages_from_require(yum_require)
- end
- end
-
- unless packages.empty?
- 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 << ", selected '#{new_package_name}' version '#{new_package_version}'"
- Chef::Log.debug(debug_msg)
-
- # Ensure it's not the same package under a different architecture
- unique_names = []
- packages.each do |pkg|
- unique_names << "#{pkg.name}-#{pkg.version.evr}"
- end
- unique_names.uniq!
-
- if unique_names.size > 1
- Chef::Log.warn("#{@new_resource} matched multiple Provides for #{@new_resource.package_name} " +
- "but we can only use the first match: #{new_package_name}. Please use a more " +
- "specific version.")
- end
-
- if yum_require.version.to_s.nil?
- new_package_version = nil
- end
+ end
- [new_package_name, new_package_version]
+ def safe_arch_array
+ if new_resource.arch.is_a?(Array)
+ new_resource.arch
+ elsif new_resource.arch.nil?
+ package_name_array.map { nil }
+ else
+ [ new_resource.arch ]
end
end
diff --git a/lib/chef/provider/package/yum/python_helper.rb b/lib/chef/provider/package/yum/python_helper.rb
new file mode 100644
index 0000000000..7758383b95
--- /dev/null
+++ b/lib/chef/provider/package/yum/python_helper.rb
@@ -0,0 +1,228 @@
+#
+# Copyright:: Copyright (c) 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_relative "../../../mixin/which"
+require_relative "../../../mixin/shell_out"
+require_relative "version"
+require "singleton" unless defined?(Singleton)
+require "timeout" unless defined?(Timeout)
+
+class Chef
+ class Provider
+ class Package
+ class Yum < Chef::Provider::Package
+ class PythonHelper
+ include Singleton
+ include Chef::Mixin::Which
+ include Chef::Mixin::ShellOut
+
+ attr_accessor :stdin
+ attr_accessor :stdout
+ attr_accessor :stderr
+ attr_accessor :inpipe
+ attr_accessor :outpipe
+ attr_accessor :wait_thr
+
+ YUM_HELPER = ::File.expand_path(::File.join(::File.dirname(__FILE__), "yum_helper.py")).freeze
+
+ def yum_command
+ @yum_command ||= begin
+ cmd = which("platform-python", "python", "python2", "python2.7", extra_path: "/usr/libexec") do |f|
+ shell_out("#{f} -c 'import yum'").exitstatus == 0
+ end
+ raise Chef::Exceptions::Package, "cannot find yum libraries, you may need to use dnf_package" unless cmd
+
+ "#{cmd} #{YUM_HELPER}"
+ end
+ end
+
+ def start
+ ENV["PYTHONUNBUFFERED"] = "1"
+ @inpipe, inpipe_write = IO.pipe
+ outpipe_read, @outpipe = IO.pipe
+ @stdin, @stdout, @stderr, @wait_thr = Open3.popen3("#{yum_command} #{outpipe_read.fileno} #{inpipe_write.fileno}", outpipe_read.fileno => outpipe_read, inpipe_write.fileno => inpipe_write, close_others: false)
+ outpipe_read.close
+ inpipe_write.close
+ end
+
+ def reap
+ unless wait_thr.nil?
+ Process.kill("INT", wait_thr.pid) rescue nil
+ begin
+ Timeout.timeout(3) do
+ wait_thr.value # this calls waitpid()
+ end
+ rescue Timeout::Error
+ Process.kill("KILL", wait_thr.pid) rescue nil
+ end
+ stdin.close unless stdin.nil?
+ stdout.close unless stdout.nil?
+ stderr.close unless stderr.nil?
+ inpipe.close unless inpipe.nil?
+ outpipe.close unless outpipe.nil?
+ end
+ end
+
+ def check
+ start if stdin.nil?
+ end
+
+ def compare_versions(version1, version2)
+ query("versioncompare", { "versions" => [version1, version2] }).to_i
+ end
+
+ def install_only_packages(name)
+ query_output = query("installonlypkgs", { "package" => name })
+ if query_output == "False"
+ false
+ elsif query_output == "True"
+ true
+ end
+ end
+
+ def options_params(options)
+ options.each_with_object({}) do |opt, h|
+ if opt =~ /--enablerepo=(.+)/
+ $1.split(",").each do |repo|
+ h["repos"] ||= []
+ h["repos"].push( { "enable" => repo } )
+ end
+ end
+ if opt =~ /--disablerepo=(.+)/
+ $1.split(",").each do |repo|
+ h["repos"] ||= []
+ h["repos"].push( { "disable" => repo } )
+ end
+ end
+ end
+ end
+
+ # @return Array<Version>
+ # NB: "options" here is the yum_package options hash and is deliberately not **opts
+ def package_query(action, provides, version: nil, arch: nil, options: {})
+ parameters = { "provides" => provides, "version" => version, "arch" => arch }
+ repo_opts = options_params(options || {})
+ parameters.merge!(repo_opts)
+ # XXX: for now we restart before and after every query with an enablerepo/disablerepo to clean the helpers internal state
+ restart unless repo_opts.empty?
+ query_output = query(action, parameters)
+ version = parse_response(query_output.lines.last)
+ Chef::Log.trace "parsed #{version} from python helper"
+ restart unless repo_opts.empty?
+ version
+ end
+
+ def restart
+ reap
+ start
+ end
+
+ private
+
+ # i couldn't figure out how to decompose an evr on the python side, it seems reasonably
+ # painless to do it in ruby (generally massaging nevras in the ruby side is HIGHLY
+ # discouraged -- this is an "every rule has an exception" exception -- any additional
+ # functionality should probably trigger moving this regexp logic into python)
+ def add_version(hash, version)
+ epoch = nil
+ if version =~ /(\S+):(\S+)/
+ epoch = $1
+ version = $2
+ end
+ if version =~ /(\S+)-(\S+)/
+ version = $1
+ release = $2
+ end
+ hash["epoch"] = epoch unless epoch.nil?
+ hash["release"] = release unless release.nil?
+ hash["version"] = version
+ end
+
+ def query(action, parameters)
+ with_helper do
+ json = build_query(action, parameters)
+ Chef::Log.trace "sending '#{json}' to python helper"
+ outpipe.syswrite json + "\n"
+ output = inpipe.sysread(4096).chomp
+ Chef::Log.trace "got '#{output}' from python helper"
+ return output
+ end
+ end
+
+ def build_query(action, parameters)
+ hash = { "action" => action }
+ parameters.each do |param_name, param_value|
+ hash[param_name] = param_value unless param_value.nil?
+ end
+
+ # Special handling for certain action / param combos
+ if %i{whatinstalled whatavailable}.include?(action)
+ add_version(hash, parameters["version"]) unless parameters["version"].nil?
+ end
+
+ FFI_Yajl::Encoder.encode(hash)
+ end
+
+ def parse_response(output)
+ array = output.split.map { |x| x == "nil" ? nil : x }
+ array.each_slice(3).map { |x| Version.new(*x) }.first
+ end
+
+ def drain_fds
+ output = ""
+ fds, = IO.select([stderr, stdout, inpipe], nil, nil, 0)
+ unless fds.nil?
+ fds.each do |fd|
+ output += fd.sysread(4096) rescue ""
+ end
+ end
+ output
+ rescue => e
+ output
+ end
+
+ def with_helper
+ max_retries ||= 5
+ ret = nil
+ Timeout.timeout(600) do
+ check
+ ret = yield
+ end
+ output = drain_fds
+ unless output.empty?
+ Chef::Log.trace "discarding output on stderr/stdout from python helper: #{output}"
+ end
+ ret
+ rescue EOFError, Errno::EPIPE, Timeout::Error, Errno::ESRCH => e
+ output = drain_fds
+ if ( max_retries -= 1 ) > 0
+ unless output.empty?
+ Chef::Log.trace "discarding output on stderr/stdout from python helper: #{output}"
+ end
+ restart
+ retry
+ else
+ raise e if output.empty?
+
+ raise "yum-helper.py had stderr/stdout output:\n\n#{output}"
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/yum/rpm_utils.rb b/lib/chef/provider/package/yum/rpm_utils.rb
index 032597d047..7514d57bce 100644
--- a/lib/chef/provider/package/yum/rpm_utils.rb
+++ b/lib/chef/provider/package/yum/rpm_utils.rb
@@ -1,6 +1,6 @@
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,7 +16,16 @@
# limitations under the License.
#
-require "chef/provider/package"
+require_relative "../../package"
+
+#
+# BUGGY AND DEPRECATED: This ruby code is known to not match the python implementation for version comparisons.
+# The APIs here should probably be converted to talk to the PythonHelper or just abandoned completely.
+#
+# e.g. this should just use Chef::Provider::Package::Yum::PythonHelper.instance.compare_versions(x,y)
+#
+# The python_helper could be extended to support additional APIs in here to remove the ruby code entirely.
+#
class Chef
class Provider
@@ -37,7 +46,7 @@ class Chef
lead = 0
tail = evr.size
- if %r{^([\d]+):}.match(evr) # rubocop:disable Performance/RedundantMatch
+ if /^(\d+):/.match(evr) # rubocop:disable Performance/RedundantMatch
epoch = $1.to_i
lead = $1.length + 1
elsif evr[0].ord == ":".ord
@@ -45,7 +54,7 @@ class Chef
lead = 1
end
- if %r{:?.*-(.*)$}.match(evr) # rubocop:disable Performance/RedundantMatch
+ if /:?.*-(.*)$/.match(evr) # rubocop:disable Performance/RedundantMatch
release = $1
tail = evr.length - release.length - lead - 1
@@ -124,9 +133,7 @@ class Chef
while (x_pos <= x_pos_max) && (isalnum(x[x_pos]) == false)
x_pos += 1 # +1 over pos_max if end of string
end
- while (y_pos <= y_pos_max) && (isalnum(y[y_pos]) == false)
- y_pos += 1
- end
+ y_pos += 1 while (y_pos <= y_pos_max) && (isalnum(y[y_pos]) == false)
# if we hit the end of either we are done matching segments
if (x_pos == x_pos_max + 1) || (y_pos == y_pos_max + 1)
@@ -145,29 +152,21 @@ class Chef
x_seg_pos += 1
# gather up our digits
- while (x_seg_pos <= x_pos_max) && isdigit(x[x_seg_pos])
- x_seg_pos += 1
- end
+ x_seg_pos += 1 while (x_seg_pos <= x_pos_max) && isdigit(x[x_seg_pos])
# copy the segment but not the unmatched character that x_seg_pos will
# refer to
x_comp = x[x_pos, x_seg_pos - x_pos]
- while (y_seg_pos <= y_pos_max) && isdigit(y[y_seg_pos])
- y_seg_pos += 1
- end
+ y_seg_pos += 1 while (y_seg_pos <= y_pos_max) && isdigit(y[y_seg_pos])
y_comp = y[y_pos, y_seg_pos - y_pos]
else
# we are comparing strings
x_seg_is_num = false
- while (x_seg_pos <= x_pos_max) && isalpha(x[x_seg_pos])
- x_seg_pos += 1
- end
+ x_seg_pos += 1 while (x_seg_pos <= x_pos_max) && isalpha(x[x_seg_pos])
x_comp = x[x_pos, x_seg_pos - x_pos]
- while (y_seg_pos <= y_pos_max) && isalpha(y[y_seg_pos])
- y_seg_pos += 1
- end
+ y_seg_pos += 1 while (y_seg_pos <= y_pos_max) && isalpha(y[y_seg_pos])
y_comp = y[y_pos, y_seg_pos - y_pos]
end
@@ -210,9 +209,9 @@ class Chef
# the most unprocessed characters left wins
if (x_pos_max - x_pos) > (y_pos_max - y_pos)
- return 1
+ 1
else
- return -1
+ -1
end
end
@@ -230,17 +229,17 @@ class Chef
@v = args[1]
@r = args[2]
else
- raise ArgumentError, "Expecting either 'epoch-version-release' or 'epoch, " +
+ raise ArgumentError, "Expecting either 'epoch-version-release' or 'epoch, " \
"version, release'"
end
end
attr_reader :e, :v, :r
- alias :epoch :e
- alias :version :v
- alias :release :r
+ alias epoch e
+ alias version v
+ alias release r
def self.parse(*args)
- self.new(*args)
+ new(*args)
end
def <=>(other)
@@ -317,7 +316,7 @@ class Chef
return cmp
end
- return 0
+ 0
end
end
@@ -339,7 +338,7 @@ class Chef
@a = args[4]
@provides = args[5]
else
- raise ArgumentError, "Expecting either 'name, epoch-version-release, arch, provides' " +
+ raise ArgumentError, "Expecting either 'name, epoch-version-release, arch, provides' " \
"or 'name, epoch, version, release, arch, provides'"
end
@@ -349,8 +348,8 @@ class Chef
end
end
attr_reader :n, :a, :version, :provides
- alias :name :n
- alias :arch :a
+ alias name n
+ alias arch a
def <=>(other)
compare(other)
@@ -395,7 +394,7 @@ class Chef
end
end
- return 0
+ 0
end
def to_s
@@ -423,7 +422,7 @@ class Chef
@version = RPMVersion.new(e, v, r)
@flag = args[4] || :==
else
- raise ArgumentError, "Expecting either 'name, epoch-version-release, flag' or " +
+ raise ArgumentError, "Expecting either 'name, epoch-version-release, flag' or " \
"'name, epoch, version, release, flag'"
end
end
@@ -434,25 +433,25 @@ class Chef
# "mtr >= 2:0.71-3.0"
# "mta"
def self.parse(string)
- if %r{^(\S+)\s+(>|>=|=|==|<=|<)\s+(\S+)$}.match(string) # rubocop:disable Performance/RedundantMatch
+ if /^(\S+)\s+(>|>=|=|==|<=|<)\s+(\S+)$/.match(string) # rubocop:disable Performance/RedundantMatch
name = $1
- if $2 == "="
- flag = :==
- else
- flag = :"#{$2}"
- end
+ flag = if $2 == "="
+ :==
+ else
+ :"#{$2}"
+ end
version = $3
- return self.new(name, version, flag)
+ new(name, version, flag)
else
name = string
- return self.new(name, nil, nil)
+ new(name, nil, nil)
end
end
# Test if another RPMDependency satisfies our requirements
def satisfy?(y)
- unless y.kind_of?(RPMDependency)
+ unless y.is_a?(RPMDependency)
raise ArgumentError, "Expecting an RPMDependency object"
end
@@ -481,7 +480,7 @@ class Chef
return true
end
- return false
+ false
end
end
@@ -504,11 +503,11 @@ class Chef
class RPMDb
def initialize
# package name => [ RPMPackage, RPMPackage ] of different versions
- @rpms = Hash.new
+ @rpms = {}
# package nevra => RPMPackage for lookups
- @index = Hash.new
+ @index = {}
# provide name (aka feature) => [RPMPackage, RPMPackage] each providing this feature
- @provides = Hash.new
+ @provides = {}
# RPMPackages listed as available
@available = Set.new
# RPMPackages listed as installed
@@ -516,16 +515,16 @@ class Chef
end
def [](package_name)
- self.lookup(package_name)
+ lookup(package_name)
end
# Lookup package_name and return a descending array of package objects
def lookup(package_name)
pkgs = @rpms[package_name]
if pkgs
- return pkgs.sort.reverse
+ pkgs.sort.reverse
else
- return nil
+ nil
end
end
@@ -537,11 +536,11 @@ class Chef
# The available/installed state can be overwritten for existing packages.
def push(*args)
args.flatten.each do |new_rpm|
- unless new_rpm.kind_of?(RPMDbPackage)
+ unless new_rpm.is_a?(RPMDbPackage)
raise ArgumentError, "Expecting an RPMDbPackage object"
end
- @rpms[new_rpm.n] ||= Array.new
+ @rpms[new_rpm.n] ||= []
# we may already have this one, like when the installed list is refreshed
idx = @index[new_rpm.nevra]
@@ -552,7 +551,7 @@ class Chef
@rpms[new_rpm.n] << new_rpm
new_rpm.provides.each do |provide|
- @provides[provide.name] ||= Array.new
+ @provides[provide.name] ||= []
@provides[provide.name] << new_rpm
end
@@ -574,7 +573,7 @@ class Chef
end
def <<(*args)
- self.push(args)
+ push(args)
end
def clear
@@ -596,7 +595,7 @@ class Chef
def size
@rpms.size
end
- alias :length :size
+ alias length size
def available_size
@available.size
@@ -615,7 +614,7 @@ class Chef
end
def whatprovides(rpmdep)
- unless rpmdep.kind_of?(RPMDependency)
+ unless rpmdep.is_a?(RPMDependency)
raise ArgumentError, "Expecting an RPMDependency object"
end
@@ -632,7 +631,7 @@ class Chef
end
end
- return what
+ what
end
end
diff --git a/lib/chef/provider/package/yum/simplejson/LICENSE.txt b/lib/chef/provider/package/yum/simplejson/LICENSE.txt
new file mode 100644
index 0000000000..e05f49c3fd
--- /dev/null
+++ b/lib/chef/provider/package/yum/simplejson/LICENSE.txt
@@ -0,0 +1,79 @@
+simplejson is dual-licensed software. It is available under the terms
+of the MIT license, or the Academic Free License version 2.1. The full
+text of each license agreement is included below. This code is also
+licensed to the Python Software Foundation (PSF) under a Contributor
+Agreement.
+
+MIT License
+===========
+
+Copyright (c) 2006 Bob Ippolito
+
+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.
+
+Academic Free License v. 2.1
+============================
+
+Copyright (c) 2006 Bob Ippolito. All rights reserved.
+
+This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following notice immediately following the copyright notice for the Original Work:
+
+Licensed under the Academic Free License version 2.1
+
+1) Grant of Copyright License. Licensor hereby grants You a world-wide, royalty-free, non-exclusive, perpetual, sublicenseable license to do the following:
+
+a) to reproduce the Original Work in copies;
+
+b) to prepare derivative works ("Derivative Works") based upon the Original Work;
+
+c) to distribute copies of the Original Work and Derivative Works to the public;
+
+d) to perform the Original Work publicly; and
+
+e) to display the Original Work publicly.
+
+2) Grant of Patent License. Licensor hereby grants You a world-wide, royalty-free, non-exclusive, perpetual, sublicenseable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, to make, use, sell and offer for sale the Original Work and Derivative Works.
+
+3) Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor hereby agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work, and by publishing the address of that information repository in a notice immediately following the copyright notice that applies to the Original Work.
+
+4) Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior written permission of the Licensor. Nothing in this License shall be deemed to grant any rights to trademarks, copyrights, patents, trade secrets or any other intellectual property of Licensor except as expressly stated herein. No patent license is granted to make, use, sell or offer to sell embodiments of any patent claims other than the licensed claims defined in Section 2. No right is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under different terms from this License any Original Work that Licensor otherwise would have a right to license.
+
+5) This section intentionally omitted.
+
+6) Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work.
+
+7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately proceeding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to Original Work is granted hereunder except under this disclaimer.
+
+8) Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to any person for any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to liability for death or personal injury resulting from Licensor's negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You.
+
+9) Acceptance and Termination. If You distribute copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. Nothing else but this License (or another written agreement between Licensor and You) grants You permission to create Derivative Works based upon the Original Work or to exercise any of the rights granted in Section 1 herein, and any attempt to do so except under the terms of this License (or another written agreement between Licensor and You) is expressly prohibited by U.S. copyright law, the equivalent laws of other countries, and by international treaty. Therefore, by exercising any of the rights granted to You in Section 1 herein, You indicate Your acceptance of this License and all of its terms and conditions.
+
+10) Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware.
+
+11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of the U.S. Copyright Act, 17 U.S.C. § 101 et seq., the equivalent laws of other countries, and international treaty. This section shall survive the termination of this License.
+
+12) Attorneys Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License.
+
+13) Miscellaneous. This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable.
+
+14) Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
+
+15) Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You.
+
+This license is Copyright (C) 2003-2004 Lawrence E. Rosen. All rights reserved. Permission is hereby granted to copy and distribute this license without modification. This license may not be modified without the express written permission of its copyright owner.
diff --git a/lib/chef/provider/package/yum/simplejson/__init__.py b/lib/chef/provider/package/yum/simplejson/__init__.py
new file mode 100644
index 0000000000..d5b4d39913
--- /dev/null
+++ b/lib/chef/provider/package/yum/simplejson/__init__.py
@@ -0,0 +1,318 @@
+r"""JSON (JavaScript Object Notation) <http://json.org> is a subset of
+JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data
+interchange format.
+
+:mod:`simplejson` exposes an API familiar to users of the standard library
+:mod:`marshal` and :mod:`pickle` modules. It is the externally maintained
+version of the :mod:`json` library contained in Python 2.6, but maintains
+compatibility with Python 2.4 and Python 2.5 and (currently) has
+significant performance advantages, even without using the optional C
+extension for speedups.
+
+Encoding basic Python object hierarchies::
+
+ >>> import simplejson as json
+ >>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
+ '["foo", {"bar": ["baz", null, 1.0, 2]}]'
+ >>> print json.dumps("\"foo\bar")
+ "\"foo\bar"
+ >>> print json.dumps(u'\u1234')
+ "\u1234"
+ >>> print json.dumps('\\')
+ "\\"
+ >>> print json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True)
+ {"a": 0, "b": 0, "c": 0}
+ >>> from StringIO import StringIO
+ >>> io = StringIO()
+ >>> json.dump(['streaming API'], io)
+ >>> io.getvalue()
+ '["streaming API"]'
+
+Compact encoding::
+
+ >>> import simplejson as json
+ >>> json.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':'))
+ '[1,2,3,{"4":5,"6":7}]'
+
+Pretty printing::
+
+ >>> import simplejson as json
+ >>> s = json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4)
+ >>> print '\n'.join([l.rstrip() for l in s.splitlines()])
+ {
+ "4": 5,
+ "6": 7
+ }
+
+Decoding JSON::
+
+ >>> import simplejson as json
+ >>> obj = [u'foo', {u'bar': [u'baz', None, 1.0, 2]}]
+ >>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') == obj
+ True
+ >>> json.loads('"\\"foo\\bar"') == u'"foo\x08ar'
+ True
+ >>> from StringIO import StringIO
+ >>> io = StringIO('["streaming API"]')
+ >>> json.load(io)[0] == 'streaming API'
+ True
+
+Specializing JSON object decoding::
+
+ >>> import simplejson as json
+ >>> def as_complex(dct):
+ ... if '__complex__' in dct:
+ ... return complex(dct['real'], dct['imag'])
+ ... return dct
+ ...
+ >>> json.loads('{"__complex__": true, "real": 1, "imag": 2}',
+ ... object_hook=as_complex)
+ (1+2j)
+ >>> import decimal
+ >>> json.loads('1.1', parse_float=decimal.Decimal) == decimal.Decimal('1.1')
+ True
+
+Specializing JSON object encoding::
+
+ >>> import simplejson as json
+ >>> def encode_complex(obj):
+ ... if isinstance(obj, complex):
+ ... return [obj.real, obj.imag]
+ ... raise TypeError(repr(o) + " is not JSON serializable")
+ ...
+ >>> json.dumps(2 + 1j, default=encode_complex)
+ '[2.0, 1.0]'
+ >>> json.JSONEncoder(default=encode_complex).encode(2 + 1j)
+ '[2.0, 1.0]'
+ >>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j))
+ '[2.0, 1.0]'
+
+
+Using simplejson.tool from the shell to validate and pretty-print::
+
+ $ echo '{"json":"obj"}' | python -m simplejson.tool
+ {
+ "json": "obj"
+ }
+ $ echo '{ 1.2:3.4}' | python -m simplejson.tool
+ Expecting property name: line 1 column 2 (char 2)
+"""
+__version__ = '2.0.9'
+__all__ = [
+ 'dump', 'dumps', 'load', 'loads',
+ 'JSONDecoder', 'JSONEncoder',
+]
+
+__author__ = 'Bob Ippolito <bob@redivi.com>'
+
+from decoder import JSONDecoder
+from encoder import JSONEncoder
+
+_default_encoder = JSONEncoder(
+ skipkeys=False,
+ ensure_ascii=True,
+ check_circular=True,
+ allow_nan=True,
+ indent=None,
+ separators=None,
+ encoding='utf-8',
+ default=None,
+)
+
+def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
+ allow_nan=True, cls=None, indent=None, separators=None,
+ encoding='utf-8', default=None, **kw):
+ """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
+ ``.write()``-supporting file-like object).
+
+ If ``skipkeys`` is true then ``dict`` keys that are not basic types
+ (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
+ will be skipped instead of raising a ``TypeError``.
+
+ If ``ensure_ascii`` is false, then the some chunks written to ``fp``
+ may be ``unicode`` instances, subject to normal Python ``str`` to
+ ``unicode`` coercion rules. Unless ``fp.write()`` explicitly
+ understands ``unicode`` (as in ``codecs.getwriter()``) this is likely
+ to cause an error.
+
+ If ``check_circular`` is false, then the circular reference check
+ for container types will be skipped and a circular reference will
+ result in an ``OverflowError`` (or worse).
+
+ If ``allow_nan`` is false, then it will be a ``ValueError`` to
+ serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``)
+ in strict compliance of the JSON specification, instead of using the
+ JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
+
+ If ``indent`` is a non-negative integer, then JSON array elements and object
+ members will be pretty-printed with that indent level. An indent level
+ of 0 will only insert newlines. ``None`` is the most compact representation.
+
+ If ``separators`` is an ``(item_separator, dict_separator)`` tuple
+ then it will be used instead of the default ``(', ', ': ')`` separators.
+ ``(',', ':')`` is the most compact JSON representation.
+
+ ``encoding`` is the character encoding for str instances, default is UTF-8.
+
+ ``default(obj)`` is a function that should return a serializable version
+ of obj or raise TypeError. The default simply raises TypeError.
+
+ To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
+ ``.default()`` method to serialize additional types), specify it with
+ the ``cls`` kwarg.
+
+ """
+ # cached encoder
+ if (not skipkeys and ensure_ascii and
+ check_circular and allow_nan and
+ cls is None and indent is None and separators is None and
+ encoding == 'utf-8' and default is None and not kw):
+ iterable = _default_encoder.iterencode(obj)
+ else:
+ if cls is None:
+ cls = JSONEncoder
+ iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii,
+ check_circular=check_circular, allow_nan=allow_nan, indent=indent,
+ separators=separators, encoding=encoding,
+ default=default, **kw).iterencode(obj)
+ # could accelerate with writelines in some versions of Python, at
+ # a debuggability cost
+ for chunk in iterable:
+ fp.write(chunk)
+
+
+def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
+ allow_nan=True, cls=None, indent=None, separators=None,
+ encoding='utf-8', default=None, **kw):
+ """Serialize ``obj`` to a JSON formatted ``str``.
+
+ If ``skipkeys`` is false then ``dict`` keys that are not basic types
+ (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
+ will be skipped instead of raising a ``TypeError``.
+
+ If ``ensure_ascii`` is false, then the return value will be a
+ ``unicode`` instance subject to normal Python ``str`` to ``unicode``
+ coercion rules instead of being escaped to an ASCII ``str``.
+
+ If ``check_circular`` is false, then the circular reference check
+ for container types will be skipped and a circular reference will
+ result in an ``OverflowError`` (or worse).
+
+ If ``allow_nan`` is false, then it will be a ``ValueError`` to
+ serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in
+ strict compliance of the JSON specification, instead of using the
+ JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
+
+ If ``indent`` is a non-negative integer, then JSON array elements and
+ object members will be pretty-printed with that indent level. An indent
+ level of 0 will only insert newlines. ``None`` is the most compact
+ representation.
+
+ If ``separators`` is an ``(item_separator, dict_separator)`` tuple
+ then it will be used instead of the default ``(', ', ': ')`` separators.
+ ``(',', ':')`` is the most compact JSON representation.
+
+ ``encoding`` is the character encoding for str instances, default is UTF-8.
+
+ ``default(obj)`` is a function that should return a serializable version
+ of obj or raise TypeError. The default simply raises TypeError.
+
+ To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
+ ``.default()`` method to serialize additional types), specify it with
+ the ``cls`` kwarg.
+
+ """
+ # cached encoder
+ if (not skipkeys and ensure_ascii and
+ check_circular and allow_nan and
+ cls is None and indent is None and separators is None and
+ encoding == 'utf-8' and default is None and not kw):
+ return _default_encoder.encode(obj)
+ if cls is None:
+ cls = JSONEncoder
+ return cls(
+ skipkeys=skipkeys, ensure_ascii=ensure_ascii,
+ check_circular=check_circular, allow_nan=allow_nan, indent=indent,
+ separators=separators, encoding=encoding, default=default,
+ **kw).encode(obj)
+
+
+_default_decoder = JSONDecoder(encoding=None, object_hook=None)
+
+
+def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None,
+ parse_int=None, parse_constant=None, **kw):
+ """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
+ a JSON document) to a Python object.
+
+ If the contents of ``fp`` is encoded with an ASCII based encoding other
+ than utf-8 (e.g. latin-1), then an appropriate ``encoding`` name must
+ be specified. Encodings that are not ASCII based (such as UCS-2) are
+ not allowed, and should be wrapped with
+ ``codecs.getreader(fp)(encoding)``, or simply decoded to a ``unicode``
+ object and passed to ``loads()``
+
+ ``object_hook`` is an optional function that will be called with the
+ result of any object literal decode (a ``dict``). The return value of
+ ``object_hook`` will be used instead of the ``dict``. This feature
+ can be used to implement custom decoders (e.g. JSON-RPC class hinting).
+
+ To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
+ kwarg.
+
+ """
+ return loads(fp.read(),
+ encoding=encoding, cls=cls, object_hook=object_hook,
+ parse_float=parse_float, parse_int=parse_int,
+ parse_constant=parse_constant, **kw)
+
+
+def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None,
+ parse_int=None, parse_constant=None, **kw):
+ """Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON
+ document) to a Python object.
+
+ If ``s`` is a ``str`` instance and is encoded with an ASCII based encoding
+ other than utf-8 (e.g. latin-1) then an appropriate ``encoding`` name
+ must be specified. Encodings that are not ASCII based (such as UCS-2)
+ are not allowed and should be decoded to ``unicode`` first.
+
+ ``object_hook`` is an optional function that will be called with the
+ result of any object literal decode (a ``dict``). The return value of
+ ``object_hook`` will be used instead of the ``dict``. This feature
+ can be used to implement custom decoders (e.g. JSON-RPC class hinting).
+
+ ``parse_float``, if specified, will be called with the string
+ of every JSON float to be decoded. By default this is equivalent to
+ float(num_str). This can be used to use another datatype or parser
+ for JSON floats (e.g. decimal.Decimal).
+
+ ``parse_int``, if specified, will be called with the string
+ of every JSON int to be decoded. By default this is equivalent to
+ int(num_str). This can be used to use another datatype or parser
+ for JSON integers (e.g. float).
+
+ ``parse_constant``, if specified, will be called with one of the
+ following strings: -Infinity, Infinity, NaN, null, true, false.
+ This can be used to raise an exception if invalid JSON numbers
+ are encountered.
+
+ To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
+ kwarg.
+
+ """
+ if (cls is None and encoding is None and object_hook is None and
+ parse_int is None and parse_float is None and
+ parse_constant is None and not kw):
+ return _default_decoder.decode(s)
+ if cls is None:
+ cls = JSONDecoder
+ if object_hook is not None:
+ kw['object_hook'] = object_hook
+ if parse_float is not None:
+ kw['parse_float'] = parse_float
+ if parse_int is not None:
+ kw['parse_int'] = parse_int
+ if parse_constant is not None:
+ kw['parse_constant'] = parse_constant
+ return cls(encoding=encoding, **kw).decode(s)
diff --git a/lib/chef/provider/package/yum/simplejson/__init__.pyc b/lib/chef/provider/package/yum/simplejson/__init__.pyc
new file mode 100644
index 0000000000..10679d3b04
--- /dev/null
+++ b/lib/chef/provider/package/yum/simplejson/__init__.pyc
Binary files differ
diff --git a/lib/chef/provider/package/yum/simplejson/decoder.py b/lib/chef/provider/package/yum/simplejson/decoder.py
new file mode 100644
index 0000000000..d921ce0b97
--- /dev/null
+++ b/lib/chef/provider/package/yum/simplejson/decoder.py
@@ -0,0 +1,354 @@
+"""Implementation of JSONDecoder
+"""
+import re
+import sys
+import struct
+
+from simplejson.scanner import make_scanner
+try:
+ from simplejson._speedups import scanstring as c_scanstring
+except ImportError:
+ c_scanstring = None
+
+__all__ = ['JSONDecoder']
+
+FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
+
+def _floatconstants():
+ _BYTES = '7FF80000000000007FF0000000000000'.decode('hex')
+ if sys.byteorder != 'big':
+ _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1]
+ nan, inf = struct.unpack('dd', _BYTES)
+ return nan, inf, -inf
+
+NaN, PosInf, NegInf = _floatconstants()
+
+
+def linecol(doc, pos):
+ lineno = doc.count('\n', 0, pos) + 1
+ if lineno == 1:
+ colno = pos
+ else:
+ colno = pos - doc.rindex('\n', 0, pos)
+ return lineno, colno
+
+
+def errmsg(msg, doc, pos, end=None):
+ # Note that this function is called from _speedups
+ lineno, colno = linecol(doc, pos)
+ if end is None:
+ #fmt = '{0}: line {1} column {2} (char {3})'
+ #return fmt.format(msg, lineno, colno, pos)
+ fmt = '%s: line %d column %d (char %d)'
+ return fmt % (msg, lineno, colno, pos)
+ endlineno, endcolno = linecol(doc, end)
+ #fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})'
+ #return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end)
+ fmt = '%s: line %d column %d - line %d column %d (char %d - %d)'
+ return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end)
+
+
+_CONSTANTS = {
+ '-Infinity': NegInf,
+ 'Infinity': PosInf,
+ 'NaN': NaN,
+}
+
+STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
+BACKSLASH = {
+ '"': u'"', '\\': u'\\', '/': u'/',
+ 'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t',
+}
+
+DEFAULT_ENCODING = "utf-8"
+
+def py_scanstring(s, end, encoding=None, strict=True, _b=BACKSLASH, _m=STRINGCHUNK.match):
+ """Scan the string s for a JSON string. End is the index of the
+ character in s after the quote that started the JSON string.
+ Unescapes all valid JSON string escape sequences and raises ValueError
+ on attempt to decode an invalid string. If strict is False then literal
+ control characters are allowed in the string.
+
+ Returns a tuple of the decoded string and the index of the character in s
+ after the end quote."""
+ if encoding is None:
+ encoding = DEFAULT_ENCODING
+ chunks = []
+ _append = chunks.append
+ begin = end - 1
+ while 1:
+ chunk = _m(s, end)
+ if chunk is None:
+ raise ValueError(
+ errmsg("Unterminated string starting at", s, begin))
+ end = chunk.end()
+ content, terminator = chunk.groups()
+ # Content is contains zero or more unescaped string characters
+ if content:
+ if not isinstance(content, unicode):
+ content = unicode(content, encoding)
+ _append(content)
+ # Terminator is the end of string, a literal control character,
+ # or a backslash denoting that an escape sequence follows
+ if terminator == '"':
+ break
+ elif terminator != '\\':
+ if strict:
+ msg = "Invalid control character %r at" % (terminator,)
+ #msg = "Invalid control character {0!r} at".format(terminator)
+ raise ValueError(errmsg(msg, s, end))
+ else:
+ _append(terminator)
+ continue
+ try:
+ esc = s[end]
+ except IndexError:
+ raise ValueError(
+ errmsg("Unterminated string starting at", s, begin))
+ # If not a unicode escape sequence, must be in the lookup table
+ if esc != 'u':
+ try:
+ char = _b[esc]
+ except KeyError:
+ msg = "Invalid \\escape: " + repr(esc)
+ raise ValueError(errmsg(msg, s, end))
+ end += 1
+ else:
+ # Unicode escape sequence
+ esc = s[end + 1:end + 5]
+ next_end = end + 5
+ if len(esc) != 4:
+ msg = "Invalid \\uXXXX escape"
+ raise ValueError(errmsg(msg, s, end))
+ uni = int(esc, 16)
+ # Check for surrogate pair on UCS-4 systems
+ if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535:
+ msg = "Invalid \\uXXXX\\uXXXX surrogate pair"
+ if not s[end + 5:end + 7] == '\\u':
+ raise ValueError(errmsg(msg, s, end))
+ esc2 = s[end + 7:end + 11]
+ if len(esc2) != 4:
+ raise ValueError(errmsg(msg, s, end))
+ uni2 = int(esc2, 16)
+ uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))
+ next_end += 6
+ char = unichr(uni)
+ end = next_end
+ # Append the unescaped character
+ _append(char)
+ return u''.join(chunks), end
+
+
+# Use speedup if available
+scanstring = c_scanstring or py_scanstring
+
+WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
+WHITESPACE_STR = ' \t\n\r'
+
+def JSONObject((s, end), encoding, strict, scan_once, object_hook, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
+ pairs = {}
+ # Use a slice to prevent IndexError from being raised, the following
+ # check will raise a more specific ValueError if the string is empty
+ nextchar = s[end:end + 1]
+ # Normally we expect nextchar == '"'
+ if nextchar != '"':
+ if nextchar in _ws:
+ end = _w(s, end).end()
+ nextchar = s[end:end + 1]
+ # Trivial empty object
+ if nextchar == '}':
+ return pairs, end + 1
+ elif nextchar != '"':
+ raise ValueError(errmsg("Expecting property name", s, end))
+ end += 1
+ while True:
+ key, end = scanstring(s, end, encoding, strict)
+
+ # To skip some function call overhead we optimize the fast paths where
+ # the JSON key separator is ": " or just ":".
+ if s[end:end + 1] != ':':
+ end = _w(s, end).end()
+ if s[end:end + 1] != ':':
+ raise ValueError(errmsg("Expecting : delimiter", s, end))
+
+ end += 1
+
+ try:
+ if s[end] in _ws:
+ end += 1
+ if s[end] in _ws:
+ end = _w(s, end + 1).end()
+ except IndexError:
+ pass
+
+ try:
+ value, end = scan_once(s, end)
+ except StopIteration:
+ raise ValueError(errmsg("Expecting object", s, end))
+ pairs[key] = value
+
+ try:
+ nextchar = s[end]
+ if nextchar in _ws:
+ end = _w(s, end + 1).end()
+ nextchar = s[end]
+ except IndexError:
+ nextchar = ''
+ end += 1
+
+ if nextchar == '}':
+ break
+ elif nextchar != ',':
+ raise ValueError(errmsg("Expecting , delimiter", s, end - 1))
+
+ try:
+ nextchar = s[end]
+ if nextchar in _ws:
+ end += 1
+ nextchar = s[end]
+ if nextchar in _ws:
+ end = _w(s, end + 1).end()
+ nextchar = s[end]
+ except IndexError:
+ nextchar = ''
+
+ end += 1
+ if nextchar != '"':
+ raise ValueError(errmsg("Expecting property name", s, end - 1))
+
+ if object_hook is not None:
+ pairs = object_hook(pairs)
+ return pairs, end
+
+def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
+ values = []
+ nextchar = s[end:end + 1]
+ if nextchar in _ws:
+ end = _w(s, end + 1).end()
+ nextchar = s[end:end + 1]
+ # Look-ahead for trivial empty array
+ if nextchar == ']':
+ return values, end + 1
+ _append = values.append
+ while True:
+ try:
+ value, end = scan_once(s, end)
+ except StopIteration:
+ raise ValueError(errmsg("Expecting object", s, end))
+ _append(value)
+ nextchar = s[end:end + 1]
+ if nextchar in _ws:
+ end = _w(s, end + 1).end()
+ nextchar = s[end:end + 1]
+ end += 1
+ if nextchar == ']':
+ break
+ elif nextchar != ',':
+ raise ValueError(errmsg("Expecting , delimiter", s, end))
+
+ try:
+ if s[end] in _ws:
+ end += 1
+ if s[end] in _ws:
+ end = _w(s, end + 1).end()
+ except IndexError:
+ pass
+
+ return values, end
+
+class JSONDecoder(object):
+ """Simple JSON <http://json.org> decoder
+
+ Performs the following translations in decoding by default:
+
+ +---------------+-------------------+
+ | JSON | Python |
+ +===============+===================+
+ | object | dict |
+ +---------------+-------------------+
+ | array | list |
+ +---------------+-------------------+
+ | string | unicode |
+ +---------------+-------------------+
+ | number (int) | int, long |
+ +---------------+-------------------+
+ | number (real) | float |
+ +---------------+-------------------+
+ | true | True |
+ +---------------+-------------------+
+ | false | False |
+ +---------------+-------------------+
+ | null | None |
+ +---------------+-------------------+
+
+ It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
+ their corresponding ``float`` values, which is outside the JSON spec.
+
+ """
+
+ def __init__(self, encoding=None, object_hook=None, parse_float=None,
+ parse_int=None, parse_constant=None, strict=True):
+ """``encoding`` determines the encoding used to interpret any ``str``
+ objects decoded by this instance (utf-8 by default). It has no
+ effect when decoding ``unicode`` objects.
+
+ Note that currently only encodings that are a superset of ASCII work,
+ strings of other encodings should be passed in as ``unicode``.
+
+ ``object_hook``, if specified, will be called with the result
+ of every JSON object decoded and its return value will be used in
+ place of the given ``dict``. This can be used to provide custom
+ deserializations (e.g. to support JSON-RPC class hinting).
+
+ ``parse_float``, if specified, will be called with the string
+ of every JSON float to be decoded. By default this is equivalent to
+ float(num_str). This can be used to use another datatype or parser
+ for JSON floats (e.g. decimal.Decimal).
+
+ ``parse_int``, if specified, will be called with the string
+ of every JSON int to be decoded. By default this is equivalent to
+ int(num_str). This can be used to use another datatype or parser
+ for JSON integers (e.g. float).
+
+ ``parse_constant``, if specified, will be called with one of the
+ following strings: -Infinity, Infinity, NaN.
+ This can be used to raise an exception if invalid JSON numbers
+ are encountered.
+
+ """
+ self.encoding = encoding
+ self.object_hook = object_hook
+ self.parse_float = parse_float or float
+ self.parse_int = parse_int or int
+ self.parse_constant = parse_constant or _CONSTANTS.__getitem__
+ self.strict = strict
+ self.parse_object = JSONObject
+ self.parse_array = JSONArray
+ self.parse_string = scanstring
+ self.scan_once = make_scanner(self)
+
+ def decode(self, s, _w=WHITESPACE.match):
+ """Return the Python representation of ``s`` (a ``str`` or ``unicode``
+ instance containing a JSON document)
+
+ """
+ obj, end = self.raw_decode(s, idx=_w(s, 0).end())
+ end = _w(s, end).end()
+ if end != len(s):
+ raise ValueError(errmsg("Extra data", s, end, len(s)))
+ return obj
+
+ def raw_decode(self, s, idx=0):
+ """Decode a JSON document from ``s`` (a ``str`` or ``unicode`` beginning
+ with a JSON document) and return a 2-tuple of the Python
+ representation and the index in ``s`` where the document ended.
+
+ This can be used to decode a JSON document from a string that may
+ have extraneous data at the end.
+
+ """
+ try:
+ obj, end = self.scan_once(s, idx)
+ except StopIteration:
+ raise ValueError("No JSON object could be decoded")
+ return obj, end
diff --git a/lib/chef/provider/package/yum/simplejson/decoder.pyc b/lib/chef/provider/package/yum/simplejson/decoder.pyc
new file mode 100644
index 0000000000..d402901870
--- /dev/null
+++ b/lib/chef/provider/package/yum/simplejson/decoder.pyc
Binary files differ
diff --git a/lib/chef/provider/package/yum/simplejson/encoder.py b/lib/chef/provider/package/yum/simplejson/encoder.py
new file mode 100644
index 0000000000..cf58290366
--- /dev/null
+++ b/lib/chef/provider/package/yum/simplejson/encoder.py
@@ -0,0 +1,440 @@
+"""Implementation of JSONEncoder
+"""
+import re
+
+try:
+ from simplejson._speedups import encode_basestring_ascii as c_encode_basestring_ascii
+except ImportError:
+ c_encode_basestring_ascii = None
+try:
+ from simplejson._speedups import make_encoder as c_make_encoder
+except ImportError:
+ c_make_encoder = None
+
+ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]')
+ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
+HAS_UTF8 = re.compile(r'[\x80-\xff]')
+ESCAPE_DCT = {
+ '\\': '\\\\',
+ '"': '\\"',
+ '\b': '\\b',
+ '\f': '\\f',
+ '\n': '\\n',
+ '\r': '\\r',
+ '\t': '\\t',
+}
+for i in range(0x20):
+ #ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))
+ ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
+
+# Assume this produces an infinity on all machines (probably not guaranteed)
+INFINITY = float('1e66666')
+FLOAT_REPR = repr
+
+def encode_basestring(s):
+ """Return a JSON representation of a Python string
+
+ """
+ def replace(match):
+ return ESCAPE_DCT[match.group(0)]
+ return '"' + ESCAPE.sub(replace, s) + '"'
+
+
+def py_encode_basestring_ascii(s):
+ """Return an ASCII-only JSON representation of a Python string
+
+ """
+ if isinstance(s, str) and HAS_UTF8.search(s) is not None:
+ s = s.decode('utf-8')
+ def replace(match):
+ s = match.group(0)
+ try:
+ return ESCAPE_DCT[s]
+ except KeyError:
+ n = ord(s)
+ if n < 0x10000:
+ #return '\\u{0:04x}'.format(n)
+ return '\\u%04x' % (n,)
+ else:
+ # surrogate pair
+ n -= 0x10000
+ s1 = 0xd800 | ((n >> 10) & 0x3ff)
+ s2 = 0xdc00 | (n & 0x3ff)
+ #return '\\u{0:04x}\\u{1:04x}'.format(s1, s2)
+ return '\\u%04x\\u%04x' % (s1, s2)
+ return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"'
+
+
+encode_basestring_ascii = c_encode_basestring_ascii or py_encode_basestring_ascii
+
+class JSONEncoder(object):
+ """Extensible JSON <http://json.org> encoder for Python data structures.
+
+ Supports the following objects and types by default:
+
+ +-------------------+---------------+
+ | Python | JSON |
+ +===================+===============+
+ | dict | object |
+ +-------------------+---------------+
+ | list, tuple | array |
+ +-------------------+---------------+
+ | str, unicode | string |
+ +-------------------+---------------+
+ | int, long, float | number |
+ +-------------------+---------------+
+ | True | true |
+ +-------------------+---------------+
+ | False | false |
+ +-------------------+---------------+
+ | None | null |
+ +-------------------+---------------+
+
+ To extend this to recognize other objects, subclass and implement a
+ ``.default()`` method with another method that returns a serializable
+ object for ``o`` if possible, otherwise it should call the superclass
+ implementation (to raise ``TypeError``).
+
+ """
+ item_separator = ', '
+ key_separator = ': '
+ def __init__(self, skipkeys=False, ensure_ascii=True,
+ check_circular=True, allow_nan=True, sort_keys=False,
+ indent=None, separators=None, encoding='utf-8', default=None):
+ """Constructor for JSONEncoder, with sensible defaults.
+
+ If skipkeys is false, then it is a TypeError to attempt
+ encoding of keys that are not str, int, long, float or None. If
+ skipkeys is True, such items are simply skipped.
+
+ If ensure_ascii is true, the output is guaranteed to be str
+ objects with all incoming unicode characters escaped. If
+ ensure_ascii is false, the output will be unicode object.
+
+ If check_circular is true, then lists, dicts, and custom encoded
+ objects will be checked for circular references during encoding to
+ prevent an infinite recursion (which would cause an OverflowError).
+ Otherwise, no such check takes place.
+
+ If allow_nan is true, then NaN, Infinity, and -Infinity will be
+ encoded as such. This behavior is not JSON specification compliant,
+ but is consistent with most JavaScript based encoders and decoders.
+ Otherwise, it will be a ValueError to encode such floats.
+
+ If sort_keys is true, then the output of dictionaries will be
+ sorted by key; this is useful for regression tests to ensure
+ that JSON serializations can be compared on a day-to-day basis.
+
+ If indent is a non-negative integer, then JSON array
+ elements and object members will be pretty-printed with that
+ indent level. An indent level of 0 will only insert newlines.
+ None is the most compact representation.
+
+ If specified, separators should be a (item_separator, key_separator)
+ tuple. The default is (', ', ': '). To get the most compact JSON
+ representation you should specify (',', ':') to eliminate whitespace.
+
+ If specified, default is a function that gets called for objects
+ that can't otherwise be serialized. It should return a JSON encodable
+ version of the object or raise a ``TypeError``.
+
+ If encoding is not None, then all input strings will be
+ transformed into unicode using that encoding prior to JSON-encoding.
+ The default is UTF-8.
+
+ """
+
+ self.skipkeys = skipkeys
+ self.ensure_ascii = ensure_ascii
+ self.check_circular = check_circular
+ self.allow_nan = allow_nan
+ self.sort_keys = sort_keys
+ self.indent = indent
+ if separators is not None:
+ self.item_separator, self.key_separator = separators
+ if default is not None:
+ self.default = default
+ self.encoding = encoding
+
+ def default(self, o):
+ """Implement this method in a subclass such that it returns
+ a serializable object for ``o``, or calls the base implementation
+ (to raise a ``TypeError``).
+
+ For example, to support arbitrary iterators, you could
+ implement default like this::
+
+ def default(self, o):
+ try:
+ iterable = iter(o)
+ except TypeError:
+ pass
+ else:
+ return list(iterable)
+ return JSONEncoder.default(self, o)
+
+ """
+ raise TypeError(repr(o) + " is not JSON serializable")
+
+ def encode(self, o):
+ """Return a JSON string representation of a Python data structure.
+
+ >>> JSONEncoder().encode({"foo": ["bar", "baz"]})
+ '{"foo": ["bar", "baz"]}'
+
+ """
+ # This is for extremely simple cases and benchmarks.
+ if isinstance(o, basestring):
+ if isinstance(o, str):
+ _encoding = self.encoding
+ if (_encoding is not None
+ and not (_encoding == 'utf-8')):
+ o = o.decode(_encoding)
+ if self.ensure_ascii:
+ return encode_basestring_ascii(o)
+ else:
+ return encode_basestring(o)
+ # This doesn't pass the iterator directly to ''.join() because the
+ # exceptions aren't as detailed. The list call should be roughly
+ # equivalent to the PySequence_Fast that ''.join() would do.
+ chunks = self.iterencode(o, _one_shot=True)
+ if not isinstance(chunks, (list, tuple)):
+ chunks = list(chunks)
+ return ''.join(chunks)
+
+ def iterencode(self, o, _one_shot=False):
+ """Encode the given object and yield each string
+ representation as available.
+
+ For example::
+
+ for chunk in JSONEncoder().iterencode(bigobject):
+ mysocket.write(chunk)
+
+ """
+ if self.check_circular:
+ markers = {}
+ else:
+ markers = None
+ if self.ensure_ascii:
+ _encoder = encode_basestring_ascii
+ else:
+ _encoder = encode_basestring
+ if self.encoding != 'utf-8':
+ def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding):
+ if isinstance(o, str):
+ o = o.decode(_encoding)
+ return _orig_encoder(o)
+
+ def floatstr(o, allow_nan=self.allow_nan, _repr=FLOAT_REPR, _inf=INFINITY, _neginf=-INFINITY):
+ # Check for specials. Note that this type of test is processor- and/or
+ # platform-specific, so do tests which don't depend on the internals.
+
+ if o != o:
+ text = 'NaN'
+ elif o == _inf:
+ text = 'Infinity'
+ elif o == _neginf:
+ text = '-Infinity'
+ else:
+ return _repr(o)
+
+ if not allow_nan:
+ raise ValueError(
+ "Out of range float values are not JSON compliant: " +
+ repr(o))
+
+ return text
+
+
+ if _one_shot and c_make_encoder is not None and not self.indent and not self.sort_keys:
+ _iterencode = c_make_encoder(
+ markers, self.default, _encoder, self.indent,
+ self.key_separator, self.item_separator, self.sort_keys,
+ self.skipkeys, self.allow_nan)
+ else:
+ _iterencode = _make_iterencode(
+ markers, self.default, _encoder, self.indent, floatstr,
+ self.key_separator, self.item_separator, self.sort_keys,
+ self.skipkeys, _one_shot)
+ return _iterencode(o, 0)
+
+def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
+ ## HACK: hand-optimized bytecode; turn globals into locals
+ False=False,
+ True=True,
+ ValueError=ValueError,
+ basestring=basestring,
+ dict=dict,
+ float=float,
+ id=id,
+ int=int,
+ isinstance=isinstance,
+ list=list,
+ long=long,
+ str=str,
+ tuple=tuple,
+ ):
+
+ def _iterencode_list(lst, _current_indent_level):
+ if not lst:
+ yield '[]'
+ return
+ if markers is not None:
+ markerid = id(lst)
+ if markerid in markers:
+ raise ValueError("Circular reference detected")
+ markers[markerid] = lst
+ buf = '['
+ if _indent is not None:
+ _current_indent_level += 1
+ newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
+ separator = _item_separator + newline_indent
+ buf += newline_indent
+ else:
+ newline_indent = None
+ separator = _item_separator
+ first = True
+ for value in lst:
+ if first:
+ first = False
+ else:
+ buf = separator
+ if isinstance(value, basestring):
+ yield buf + _encoder(value)
+ elif value is None:
+ yield buf + 'null'
+ elif value is True:
+ yield buf + 'true'
+ elif value is False:
+ yield buf + 'false'
+ elif isinstance(value, (int, long)):
+ yield buf + str(value)
+ elif isinstance(value, float):
+ yield buf + _floatstr(value)
+ else:
+ yield buf
+ if isinstance(value, (list, tuple)):
+ chunks = _iterencode_list(value, _current_indent_level)
+ elif isinstance(value, dict):
+ chunks = _iterencode_dict(value, _current_indent_level)
+ else:
+ chunks = _iterencode(value, _current_indent_level)
+ for chunk in chunks:
+ yield chunk
+ if newline_indent is not None:
+ _current_indent_level -= 1
+ yield '\n' + (' ' * (_indent * _current_indent_level))
+ yield ']'
+ if markers is not None:
+ del markers[markerid]
+
+ def _iterencode_dict(dct, _current_indent_level):
+ if not dct:
+ yield '{}'
+ return
+ if markers is not None:
+ markerid = id(dct)
+ if markerid in markers:
+ raise ValueError("Circular reference detected")
+ markers[markerid] = dct
+ yield '{'
+ if _indent is not None:
+ _current_indent_level += 1
+ newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
+ item_separator = _item_separator + newline_indent
+ yield newline_indent
+ else:
+ newline_indent = None
+ item_separator = _item_separator
+ first = True
+ if _sort_keys:
+ items = dct.items()
+ items.sort(key=lambda kv: kv[0])
+ else:
+ items = dct.iteritems()
+ for key, value in items:
+ if isinstance(key, basestring):
+ pass
+ # JavaScript is weakly typed for these, so it makes sense to
+ # also allow them. Many encoders seem to do something like this.
+ elif isinstance(key, float):
+ key = _floatstr(key)
+ elif key is True:
+ key = 'true'
+ elif key is False:
+ key = 'false'
+ elif key is None:
+ key = 'null'
+ elif isinstance(key, (int, long)):
+ key = str(key)
+ elif _skipkeys:
+ continue
+ else:
+ raise TypeError("key " + repr(key) + " is not a string")
+ if first:
+ first = False
+ else:
+ yield item_separator
+ yield _encoder(key)
+ yield _key_separator
+ if isinstance(value, basestring):
+ yield _encoder(value)
+ elif value is None:
+ yield 'null'
+ elif value is True:
+ yield 'true'
+ elif value is False:
+ yield 'false'
+ elif isinstance(value, (int, long)):
+ yield str(value)
+ elif isinstance(value, float):
+ yield _floatstr(value)
+ else:
+ if isinstance(value, (list, tuple)):
+ chunks = _iterencode_list(value, _current_indent_level)
+ elif isinstance(value, dict):
+ chunks = _iterencode_dict(value, _current_indent_level)
+ else:
+ chunks = _iterencode(value, _current_indent_level)
+ for chunk in chunks:
+ yield chunk
+ if newline_indent is not None:
+ _current_indent_level -= 1
+ yield '\n' + (' ' * (_indent * _current_indent_level))
+ yield '}'
+ if markers is not None:
+ del markers[markerid]
+
+ def _iterencode(o, _current_indent_level):
+ if isinstance(o, basestring):
+ yield _encoder(o)
+ elif o is None:
+ yield 'null'
+ elif o is True:
+ yield 'true'
+ elif o is False:
+ yield 'false'
+ elif isinstance(o, (int, long)):
+ yield str(o)
+ elif isinstance(o, float):
+ yield _floatstr(o)
+ elif isinstance(o, (list, tuple)):
+ for chunk in _iterencode_list(o, _current_indent_level):
+ yield chunk
+ elif isinstance(o, dict):
+ for chunk in _iterencode_dict(o, _current_indent_level):
+ yield chunk
+ else:
+ if markers is not None:
+ markerid = id(o)
+ if markerid in markers:
+ raise ValueError("Circular reference detected")
+ markers[markerid] = o
+ o = _default(o)
+ for chunk in _iterencode(o, _current_indent_level):
+ yield chunk
+ if markers is not None:
+ del markers[markerid]
+
+ return _iterencode
diff --git a/lib/chef/provider/package/yum/simplejson/encoder.pyc b/lib/chef/provider/package/yum/simplejson/encoder.pyc
new file mode 100644
index 0000000000..207bce5cfb
--- /dev/null
+++ b/lib/chef/provider/package/yum/simplejson/encoder.pyc
Binary files differ
diff --git a/lib/chef/provider/package/yum/simplejson/scanner.py b/lib/chef/provider/package/yum/simplejson/scanner.py
new file mode 100644
index 0000000000..adbc6ec979
--- /dev/null
+++ b/lib/chef/provider/package/yum/simplejson/scanner.py
@@ -0,0 +1,65 @@
+"""JSON token scanner
+"""
+import re
+try:
+ from simplejson._speedups import make_scanner as c_make_scanner
+except ImportError:
+ c_make_scanner = None
+
+__all__ = ['make_scanner']
+
+NUMBER_RE = re.compile(
+ r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
+ (re.VERBOSE | re.MULTILINE | re.DOTALL))
+
+def py_make_scanner(context):
+ parse_object = context.parse_object
+ parse_array = context.parse_array
+ parse_string = context.parse_string
+ match_number = NUMBER_RE.match
+ encoding = context.encoding
+ strict = context.strict
+ parse_float = context.parse_float
+ parse_int = context.parse_int
+ parse_constant = context.parse_constant
+ object_hook = context.object_hook
+
+ def _scan_once(string, idx):
+ try:
+ nextchar = string[idx]
+ except IndexError:
+ raise StopIteration
+
+ if nextchar == '"':
+ return parse_string(string, idx + 1, encoding, strict)
+ elif nextchar == '{':
+ return parse_object((string, idx + 1), encoding, strict, _scan_once, object_hook)
+ elif nextchar == '[':
+ return parse_array((string, idx + 1), _scan_once)
+ elif nextchar == 'n' and string[idx:idx + 4] == 'null':
+ return None, idx + 4
+ elif nextchar == 't' and string[idx:idx + 4] == 'true':
+ return True, idx + 4
+ elif nextchar == 'f' and string[idx:idx + 5] == 'false':
+ return False, idx + 5
+
+ m = match_number(string, idx)
+ if m is not None:
+ integer, frac, exp = m.groups()
+ if frac or exp:
+ res = parse_float(integer + (frac or '') + (exp or ''))
+ else:
+ res = parse_int(integer)
+ return res, m.end()
+ elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
+ return parse_constant('NaN'), idx + 3
+ elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
+ return parse_constant('Infinity'), idx + 8
+ elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
+ return parse_constant('-Infinity'), idx + 9
+ else:
+ raise StopIteration
+
+ return _scan_once
+
+make_scanner = c_make_scanner or py_make_scanner
diff --git a/lib/chef/provider/package/yum/simplejson/scanner.pyc b/lib/chef/provider/package/yum/simplejson/scanner.pyc
new file mode 100644
index 0000000000..12df070e44
--- /dev/null
+++ b/lib/chef/provider/package/yum/simplejson/scanner.pyc
Binary files differ
diff --git a/lib/chef/provider/package/yum/simplejson/tool.py b/lib/chef/provider/package/yum/simplejson/tool.py
new file mode 100644
index 0000000000..90443317b2
--- /dev/null
+++ b/lib/chef/provider/package/yum/simplejson/tool.py
@@ -0,0 +1,37 @@
+r"""Command-line tool to validate and pretty-print JSON
+
+Usage::
+
+ $ echo '{"json":"obj"}' | python -m simplejson.tool
+ {
+ "json": "obj"
+ }
+ $ echo '{ 1.2:3.4}' | python -m simplejson.tool
+ Expecting property name: line 1 column 2 (char 2)
+
+"""
+import sys
+import simplejson
+
+def main():
+ if len(sys.argv) == 1:
+ infile = sys.stdin
+ outfile = sys.stdout
+ elif len(sys.argv) == 2:
+ infile = open(sys.argv[1], 'rb')
+ outfile = sys.stdout
+ elif len(sys.argv) == 3:
+ infile = open(sys.argv[1], 'rb')
+ outfile = open(sys.argv[2], 'wb')
+ else:
+ raise SystemExit(sys.argv[0] + " [infile [outfile]]")
+ try:
+ obj = simplejson.load(infile)
+ except ValueError, e:
+ raise SystemExit(e)
+ simplejson.dump(obj, outfile, sort_keys=True, indent=4)
+ outfile.write('\n')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/chef/provider/package/yum/version.rb b/lib/chef/provider/package/yum/version.rb
new file mode 100644
index 0000000000..6107a54532
--- /dev/null
+++ b/lib/chef/provider/package/yum/version.rb
@@ -0,0 +1,60 @@
+#
+# Copyright:: Copyright (c) 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 Provider
+ class Package
+ class Yum < Chef::Provider::Package
+
+ # helper class to assist in passing around name/version/arch triples
+ class Version
+ attr_accessor :name
+ attr_accessor :version
+ attr_accessor :arch
+
+ def initialize(name, version, arch)
+ @name = name
+ @version = version
+ @arch = arch
+ end
+
+ def to_s
+ "#{name}-#{version}.#{arch}" unless version.nil?
+ end
+
+ def version_with_arch
+ "#{version}.#{arch}" unless version.nil?
+ end
+
+ def name_with_arch
+ "#{name}.#{arch}" unless name.nil?
+ end
+
+ def matches_name_and_arch?(other)
+ other.version == version && other.arch == arch
+ end
+
+ def ==(other)
+ name == other.name && version == other.version && arch == other.arch
+ end
+
+ alias eql? ==
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/yum/yum-dump.py b/lib/chef/provider/package/yum/yum-dump.py
deleted file mode 100644
index 6183460195..0000000000
--- a/lib/chef/provider/package/yum/yum-dump.py
+++ /dev/null
@@ -1,307 +0,0 @@
-#
-# Author:: Matthew Kent (<mkent@magoazul.com>)
-# Copyright:: Copyright 2009-2016, Matthew Kent
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# yum-dump.py
-# Inspired by yumhelper.py by David Lutterkort
-#
-# Produce a list of installed, available and re-installable packages using yum
-# and dump the results to stdout.
-#
-# yum-dump invokes yum similarly to the command line interface which makes it
-# subject to most of the configuration parameters in yum.conf. yum-dump will
-# also load yum plugins in the same manor as yum - these can affect the output.
-#
-# Can be run as non root, but that won't update the cache.
-#
-# Intended to support yum 2.x and 3.x
-
-import os
-import sys
-import time
-import yum
-import re
-import errno
-
-from yum import Errors
-from optparse import OptionParser
-from distutils import version
-
-YUM_PID_FILE='/var/run/yum.pid'
-
-YUM_VER = version.StrictVersion(yum.__version__)
-YUM_MAJOR = YUM_VER.version[0]
-
-if YUM_MAJOR > 3 or YUM_MAJOR < 2:
- print >> sys.stderr, "yum-dump Error: Can't match supported yum version" \
- " (%s)" % yum.__version__
- sys.exit(1)
-
-# Required for Provides output
-if YUM_MAJOR == 2:
- import rpm
- import rpmUtils.miscutils
-
-def setup(yb, options):
- # Only want our output
- #
- if YUM_MAJOR == 3:
- try:
- if YUM_VER >= version.StrictVersion("3.2.22"):
- yb.preconf.errorlevel=0
- yb.preconf.debuglevel=0
-
- # initialize the config
- yb.conf
- else:
- yb.doConfigSetup(errorlevel=0, debuglevel=0)
- except yum.Errors.ConfigError, e:
- # suppresses an ignored exception at exit
- yb.preconf = None
- print >> sys.stderr, "yum-dump Config Error: %s" % e
- return 1
- except ValueError, e:
- yb.preconf = None
- print >> sys.stderr, "yum-dump Options Error: %s" % e
- return 1
- elif YUM_MAJOR == 2:
- yb.doConfigSetup()
-
- def __log(a,b): pass
-
- yb.log = __log
- yb.errorlog = __log
-
- # Give Chef every possible package version, it can decide what to do with them
- if YUM_MAJOR == 3:
- yb.conf.showdupesfromrepos = True
- elif YUM_MAJOR == 2:
- yb.conf.setConfigOption('showdupesfromrepos', True)
-
- # Optionally run only on cached repositories, but non root must use the cache
- if os.geteuid() != 0:
- if YUM_MAJOR == 3:
- yb.conf.cache = True
- elif YUM_MAJOR == 2:
- yb.conf.setConfigOption('cache', True)
- else:
- if YUM_MAJOR == 3:
- yb.conf.cache = options.cache
- elif YUM_MAJOR == 2:
- yb.conf.setConfigOption('cache', options.cache)
-
- # Handle repo toggle via id or glob exactly like yum
- for opt, repos in options.repo_control:
- for repo in repos:
- if opt == '--enablerepo':
- yb.repos.enableRepo(repo)
- elif opt == '--disablerepo':
- yb.repos.disableRepo(repo)
-
- return 0
-
-def dump_packages(yb, list, output_provides):
- packages = {}
-
- if YUM_MAJOR == 2:
- yb.doTsSetup()
- yb.doRepoSetup()
- yb.doSackSetup()
-
- db = yb.doPackageLists(list)
-
- for pkg in db.installed:
- pkg.type = 'i'
- packages[str(pkg)] = pkg
-
- if YUM_VER >= version.StrictVersion("3.2.21"):
- for pkg in db.available:
- pkg.type = 'a'
- packages[str(pkg)] = pkg
-
- # These are both installed and available
- for pkg in db.reinstall_available:
- pkg.type = 'r'
- packages[str(pkg)] = pkg
- else:
- # Old style method - no reinstall list
- for pkg in yb.pkgSack.returnPackages():
-
- if str(pkg) in packages:
- if packages[str(pkg)].type == "i":
- packages[str(pkg)].type = 'r'
- continue
-
- pkg.type = 'a'
- packages[str(pkg)] = pkg
-
- unique_packages = packages.values()
-
- unique_packages.sort(lambda x, y: cmp(x.name, y.name))
-
- for pkg in unique_packages:
- if output_provides == "all" or \
- (output_provides == "installed" and (pkg.type == "i" or pkg.type == "r")):
-
- # yum 2 doesn't have provides_print, implement it ourselves using methods
- # based on requires gathering in packages.py
- if YUM_MAJOR == 2:
- provlist = []
-
- # Installed and available are gathered in different ways
- if pkg.type == 'i' or pkg.type == 'r':
- names = pkg.hdr[rpm.RPMTAG_PROVIDENAME]
- flags = pkg.hdr[rpm.RPMTAG_PROVIDEFLAGS]
- ver = pkg.hdr[rpm.RPMTAG_PROVIDEVERSION]
- if names is not None:
- tmplst = zip(names, flags, ver)
-
- for (n, f, v) in tmplst:
- prov = rpmUtils.miscutils.formatRequire(n, v, f)
- provlist.append(prov)
- # This is slow :(
- elif pkg.type == 'a':
- for prcoTuple in pkg.returnPrco('provides'):
- prcostr = pkg.prcoPrintable(prcoTuple)
- provlist.append(prcostr)
-
- provides = provlist
- else:
- provides = pkg.provides_print
- else:
- provides = "[]"
-
- print '%s %s %s %s %s %s %s %s' % (
- pkg.name,
- pkg.epoch,
- pkg.version,
- pkg.release,
- pkg.arch,
- provides,
- pkg.type,
- pkg.repoid )
-
- return 0
-
-def yum_dump(options):
- lock_obtained = False
-
- yb = yum.YumBase()
-
- status = setup(yb, options)
- if status != 0:
- return status
-
- if options.output_options:
- print "[option installonlypkgs] %s" % " ".join(yb.conf.installonlypkgs)
-
- # Non root can't handle locking on rhel/centos 4
- if os.geteuid() != 0:
- return dump_packages(yb, options.package_list, options.output_provides)
-
- # Wrap the collection and output of packages in yum's global lock to prevent
- # any inconsistencies.
- try:
- # Spin up to --yum-lock-timeout option
- countdown = options.yum_lock_timeout
- while True:
- try:
- yb.doLock(YUM_PID_FILE)
- lock_obtained = True
- except Errors.LockError, e:
- time.sleep(1)
- countdown -= 1
- if countdown == 0:
- print >> sys.stderr, "yum-dump Locking Error! Couldn't obtain an " \
- "exclusive yum lock in %d seconds. Giving up." % options.yum_lock_timeout
- return 200
- else:
- break
-
- return dump_packages(yb, options.package_list, options.output_provides)
-
- # Ensure we clear the lock and cleanup any resources
- finally:
- try:
- yb.closeRpmDB()
- if lock_obtained == True:
- yb.doUnlock(YUM_PID_FILE)
- except Errors.LockError, e:
- print >> sys.stderr, "yum-dump Unlock Error: %s" % e
- return 200
-
-# Preserve order of enable/disable repo args like yum does
-def gather_repo_opts(option, opt, value, parser):
- if getattr(parser.values, option.dest, None) is None:
- setattr(parser.values, option.dest, [])
- getattr(parser.values, option.dest).append((opt, value.split(',')))
-
-def main():
- usage = "Usage: %prog [options]\n" + \
- "Output a list of installed, available and re-installable packages via yum"
- parser = OptionParser(usage=usage)
- parser.add_option("-C", "--cache",
- action="store_true", dest="cache", default=False,
- help="run entirely from cache, don't update cache")
- parser.add_option("-o", "--options",
- action="store_true", dest="output_options", default=False,
- help="output select yum options useful to Chef")
- parser.add_option("-p", "--installed-provides",
- action="store_const", const="installed", dest="output_provides", default="none",
- help="output Provides for installed packages, big/wide output")
- parser.add_option("-P", "--all-provides",
- action="store_const", const="all", dest="output_provides", default="none",
- help="output Provides for all package, slow, big/wide output")
- parser.add_option("-i", "--installed",
- action="store_const", const="installed", dest="package_list", default="all",
- help="output only installed packages")
- parser.add_option("-a", "--available",
- action="store_const", const="available", dest="package_list", default="all",
- help="output only available and re-installable packages")
- parser.add_option("--enablerepo",
- action="callback", callback=gather_repo_opts, type="string", dest="repo_control", default=[],
- help="enable disabled repositories by id or glob")
- parser.add_option("--disablerepo",
- action="callback", callback=gather_repo_opts, type="string", dest="repo_control", default=[],
- help="disable repositories by id or glob")
- parser.add_option("--yum-lock-timeout",
- action="store", type="int", dest="yum_lock_timeout", default=30,
- help="Time in seconds to wait for yum process lock")
-
- (options, args) = parser.parse_args()
-
- try:
- return yum_dump(options)
-
- except yum.Errors.RepoError, e:
- print >> sys.stderr, "yum-dump Repository Error: %s" % e
- return 1
-
- except yum.Errors.YumBaseError, e:
- print >> sys.stderr, "yum-dump General Error: %s" % e
- return 1
-
-try:
- status = main()
-# Suppress a nasty broken pipe error when output is piped to utilities like 'head'
-except IOError, e:
- if e.errno == errno.EPIPE:
- sys.exit(1)
- else:
- raise
-
-sys.exit(status)
diff --git a/lib/chef/provider/package/yum/yum_cache.rb b/lib/chef/provider/package/yum/yum_cache.rb
index 7462529113..b65e1e560f 100644
--- a/lib/chef/provider/package/yum/yum_cache.rb
+++ b/lib/chef/provider/package/yum/yum_cache.rb
@@ -1,6 +1,6 @@
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,357 +16,74 @@
# limitations under the License.
#
-require "chef/config"
-require "chef/provider/package"
-require "chef/mixin/which"
-require "chef/mixin/shell_out"
-require "singleton"
-require "chef/provider/package/yum/rpm_utils"
+require_relative "python_helper"
+require_relative "../../package"
+require "singleton" unless defined?(Singleton)
+
+#
+# These are largely historical APIs, the YumCache object no longer exists and this is a
+# facade over the python helper class. It should be considered deprecated-lite and
+# no new APIs should be added and should be added to the python_helper instead.
+#
class Chef
class Provider
class Package
class Yum < Chef::Provider::Package
- # Cache for our installed and available packages, pulled in from yum-dump.py
class YumCache
- include Chef::Mixin::Which
- include Chef::Mixin::ShellOut
include Singleton
- attr_accessor :yum_binary
-
- def initialize
- @rpmdb = RPMDb.new
-
- # Next time @rpmdb is accessed:
- # :all - Trigger a run of "yum-dump.py --options --installed-provides", updates
- # yum's cache and parses options from /etc/yum.conf. Pulls in Provides
- # dependency data for installed packages only - this data is slow to
- # gather.
- # :provides - Same as :all but pulls in Provides data for available packages as well.
- # Used as a last resort when we can't find a Provides match.
- # :installed - Trigger a run of "yum-dump.py --installed", only reads the local rpm
- # db. Used between client runs for a quick refresh.
- # :none - Do nothing, a call to one of the reload methods is required.
- @next_refresh = :all
-
- @allow_multi_install = []
-
- @extra_repo_control = nil
-
- # these are for subsequent runs if we are on an interval
- Chef::Client.when_run_starts do
- YumCache.instance.reload
- end
- end
-
- attr_reader :extra_repo_control
-
- # Cache management
- #
-
- def yum_dump_path
- ::File.join(::File.dirname(__FILE__), "yum-dump.py")
- end
-
def refresh
- case @next_refresh
- when :none
- return nil
- when :installed
- reset_installed
- # fast
- opts = " --installed"
- when :all
- reset
- # medium
- opts = " --options --installed-provides"
- when :provides
- reset
- # slow!
- opts = " --options --all-provides"
- else
- raise ArgumentError, "Unexpected value in next_refresh: #{@next_refresh}"
- end
-
- if @extra_repo_control
- opts << " #{@extra_repo_control}"
- end
-
- opts << " --yum-lock-timeout #{Chef::Config[:yum_lock_timeout]}"
-
- one_line = false
- error = nil
-
- status = nil
-
- begin
- status = shell_out!("#{python_bin} #{yum_dump_path}#{opts}", :timeout => Chef::Config[:yum_timeout])
- status.stdout.each_line do |line|
- one_line = true
-
- line.chomp!
- if line =~ %r{\[option (.*)\] (.*)}
- if $1 == "installonlypkgs"
- @allow_multi_install = $2.split
- else
- raise Chef::Exceptions::Package, "Strange, unknown option line '#{line}' from yum-dump.py"
- end
- next
- end
-
- if line =~ %r{^(\S+) ([0-9]+) (\S+) (\S+) (\S+) \[(.*)\] ([i,a,r]) (\S+)$}
- name = $1
- epoch = $2
- version = $3
- release = $4
- arch = $5
- provides = parse_provides($6)
- type = $7
- repoid = $8
- else
- Chef::Log.warn("Problem parsing line '#{line}' from yum-dump.py! " +
- "Please check your yum configuration.")
- next
- end
-
- case type
- when "i"
- # if yum-dump was called with --installed this may not be true, but it's okay
- # since we don't touch the @available Set in reload_installed
- available = false
- installed = true
- when "a"
- available = true
- installed = false
- when "r"
- available = true
- installed = true
- end
-
- pkg = RPMDbPackage.new(name, epoch, version, release, arch, provides, installed, available, repoid)
- @rpmdb << pkg
- end
-
- error = status.stderr
- rescue Mixlib::ShellOut::CommandTimeout => e
- Chef::Log.error("#{yum_dump_path} exceeded timeout #{Chef::Config[:yum_timeout]}")
- raise(e)
- end
-
- if status.exitstatus != 0
- raise Chef::Exceptions::Package, "Yum failed - #{status.inspect} - returns: #{error}"
- else
- unless one_line
- Chef::Log.warn("Odd, no output from yum-dump.py. Please check " +
- "your yum configuration.")
- end
- end
-
- # A reload method must be called before the cache is altered
- @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
+ python_helper.restart
end
def reload
- @next_refresh = :all
+ python_helper.restart
end
def reload_installed
- @next_refresh = :installed
+ python_helper.restart
end
def reload_provides
- @next_refresh = :provides
+ python_helper.restart
end
def reset
- @rpmdb.clear
+ python_helper.restart
end
def reset_installed
- @rpmdb.clear_installed
+ python_helper.restart
end
- # Querying the cache
- #
-
- # Check for package by name or name+arch
- def package_available?(package_name)
- refresh
-
- if @rpmdb.lookup(package_name)
- return true
- else
- if package_name =~ %r{^(.*)\.(.*)$}
- pkg_name = $1
- pkg_arch = $2
-
- if matches = @rpmdb.lookup(pkg_name)
- matches.each do |m|
- return true if m.arch == pkg_arch
- end
- end
- end
- end
-
- return false
+ def available_version(name, arch = nil)
+ p = python_helper.package_query(:whatavailable, name, arch: arch)
+ "#{p.version}.#{p.arch}" unless p.version.nil?
end
- # Returns a array of packages satisfying an RPMDependency
- def packages_from_require(rpmdep)
- refresh
- @rpmdb.whatprovides(rpmdep)
+ def installed_version(name, arch = nil)
+ p = python_helper.package_query(:whatinstalled, name, arch: arch)
+ "#{p.version}.#{p.arch}" unless p.version.nil?
end
- # Check if a package-version.arch is available to install
- def version_available?(package_name, desired_version, arch = nil)
- version(package_name, arch, true, false) do |v|
- return true if desired_version == v
- end
-
- return false
- end
-
- # Return the source repository for a package-version.arch
- def package_repository(package_name, desired_version, arch = nil)
- package(package_name, arch, true, false) do |pkg|
- return pkg.repoid if desired_version == pkg.version.to_s
- end
-
- return nil
+ def package_available?(name, arch = nil)
+ p = python_helper.package_query(:whatavailable, name, arch: arch)
+ !p.version.nil?
end
- # Return the latest available version for a package.arch
- def available_version(package_name, arch = nil)
- version(package_name, arch, true, false)
+ # NOTE that it is the responsibility of the python_helper to get these APIs correct and
+ # we do not do any validation here that the e.g. version or arch matches the requested value
+ # (because the bigger issue there is a buggy+broken python_helper -- so don't try to fix those
+ # kinds of bugs here)
+ def version_available?(name, version, arch = nil)
+ p = python_helper.package_query(:whatavailable, name, version: version, arch: arch)
+ !p.version.nil?
end
- alias :candidate_version :available_version
-
- # Return the currently installed version for a package.arch
- def installed_version(package_name, arch = nil)
- version(package_name, arch, false, true)
- end
-
- # Return an array of packages allowed to be installed multiple times, such as the kernel
- def allow_multi_install
- refresh
- @allow_multi_install
- end
-
- def enable_extra_repo_control(arg)
- # Don't touch cache if it's the same repos as the last load
- unless @extra_repo_control == arg
- @extra_repo_control = arg
- reload
- end
- end
-
- def disable_extra_repo_control
- # Only force reload when set
- if @extra_repo_control
- @extra_repo_control = nil
- reload
- end
- end
-
- private
-
- def version(package_name, arch = nil, is_available = false, is_installed = false)
- package(package_name, arch, is_available, is_installed) do |pkg|
- if block_given?
- yield pkg.version.to_s
- else
- # first match is latest version
- return pkg.version.to_s
- end
- end
-
- if block_given?
- return self
- else
- return nil
- end
- end
-
- def package(package_name, arch = nil, is_available = false, is_installed = false)
- refresh
- packages = @rpmdb[package_name]
- if packages
- packages.each do |pkg|
- if is_available
- next unless @rpmdb.available?(pkg)
- end
- if is_installed
- next unless @rpmdb.installed?(pkg)
- end
- if arch
- next unless pkg.arch == arch
- end
-
- if block_given?
- yield pkg
- else
- # first match is latest version
- return pkg
- end
- end
- end
-
- if block_given?
- return self
- else
- return nil
- end
- end
-
- # Parse provides from yum-dump.py output
- def parse_provides(string)
- ret = []
- # ['atk = 1.12.2-1.fc6', 'libatk-1.0.so.0']
- string.split(", ").each do |seg|
- # 'atk = 1.12.2-1.fc6'
- if seg =~ %r{^'(.*)'$}
- ret << RPMProvide.parse($1)
- end
- end
- return ret
+ # @api private
+ def python_helper
+ @python_helper ||= PythonHelper.instance
end
end # YumCache
diff --git a/lib/chef/provider/package/yum/yum_helper.py b/lib/chef/provider/package/yum/yum_helper.py
new file mode 100644
index 0000000000..47cbe2efe6
--- /dev/null
+++ b/lib/chef/provider/package/yum/yum_helper.py
@@ -0,0 +1,216 @@
+#!/usr/bin/env python3
+# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
+
+#
+# NOTE: this actually needs to run under python2.4 and centos 5.x through python3 and centos 7.x
+# please manually test changes on centos5 boxes or you will almost certainly break things.
+#
+
+import sys
+import yum
+import signal
+import os
+sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'simplejson'))
+try: import json
+except ImportError: import simplejson as json
+import re
+from rpmUtils.miscutils import stringToVersion,compareEVR
+from rpmUtils.arch import getBaseArch, getArchList
+
+
+try: from yum.misc import string_to_prco_tuple
+except ImportError:
+ # RHEL5 compat
+ def string_to_prco_tuple(prcoString):
+ prco_split = prcoString.split()
+ n, f, v = prco_split
+ (prco_e, prco_v, prco_r) = stringToVersion(v)
+ return (n, f, (prco_e, prco_v, prco_r))
+
+# hack to work around https://github.com/chef/chef/issues/7126
+# see https://bugzilla.redhat.com/show_bug.cgi?id=1396248
+if not hasattr(yum.packages.FakeRepository, 'compare_providers_priority'):
+ yum.packages.FakeRepository.compare_providers_priority = 99
+
+base = None
+
+def get_base():
+ global base
+ if base is None:
+ base = yum.YumBase()
+ setup_exit_handler()
+ return base
+
+def versioncompare(versions):
+ arch_list = getArchList()
+ candidate_arch1 = versions[0].split(".")[-1]
+ candidate_arch2 = versions[1].split(".")[-1]
+
+ # The first version number passed to this method is always a valid nevra (the current version)
+ # If the second version number looks like it does not contain a valid arch
+ # then we'll chop the arch component (assuming it *is* a valid one) from the first version string
+ # so we're only comparing the evr portions.
+ if (candidate_arch2 not in arch_list) and (candidate_arch1 in arch_list):
+ final_version1 = versions[0].replace("." + candidate_arch1,"")
+ else:
+ final_version1 = versions[0]
+
+ final_version2 = versions[1]
+
+ (e1, v1, r1) = stringToVersion(final_version1)
+ (e2, v2, r2) = stringToVersion(final_version2)
+
+ evr_comparison = compareEVR((e1, v1, r1), (e2, v2, r2))
+ outpipe.write("%(e)s\n" % { 'e': evr_comparison })
+ outpipe.flush()
+
+def install_only_packages(name):
+ base = get_base()
+ if name in base.conf.installonlypkgs:
+ outpipe.write('True')
+ else:
+ outpipe.write('False')
+ outpipe.flush()
+
+# python2.4 / centos5 compat
+try:
+ any
+except NameError:
+ def any(s):
+ for v in s:
+ if v:
+ return True
+ return False
+
+def query(command):
+ base = get_base()
+
+ enabled_repos = base.repos.listEnabled()
+
+ # Handle any repocontrols passed in with our options
+
+ if 'repos' in command:
+ for repo in command['repos']:
+ if 'enable' in repo:
+ base.repos.enableRepo(repo['enable'])
+ if 'disable' in repo:
+ base.repos.disableRepo(repo['disable'])
+
+ args = { 'name': command['provides'] }
+ do_nevra = False
+ if 'epoch' in command:
+ args['epoch'] = command['epoch']
+ do_nevra = True
+ if 'version' in command:
+ args['ver'] = command['version']
+ do_nevra = True
+ if 'release' in command:
+ args['rel'] = command['release']
+ do_nevra = True
+ if 'arch' in command:
+ desired_arch = command['arch']
+ args['arch'] = command['arch']
+ do_nevra = True
+ else:
+ desired_arch = getBaseArch()
+
+ obj = None
+ if command['action'] == "whatinstalled":
+ obj = base.rpmdb
+ else:
+ obj = base.pkgSack
+
+ # if we are given "name == 1.2.3" then we must use the getProvides() API.
+ # - this means that we ignore arch and version properties when given prco tuples as a package_name
+ # - in order to fix this, something would have to happen where getProvides was called first and
+ # then the result was searchNevra'd. please be extremely careful if attempting to fix that
+ # since searchNevra does not support prco tuples.
+ if bool(re.search('\\s+', command['provides'])):
+ # handles flags (<, >, =, etc) and versions, but no wildcareds
+ # raises error for any invalid input like: 'FOO BAR BAZ'
+ pkgs = obj.getProvides(*string_to_prco_tuple(command['provides']))
+ elif do_nevra:
+ # now if we're given version or arch properties explicitly, then we do a SearchNevra.
+ # - this means that wildcard version in the package_name with an arch property will not work correctly
+ # - again don't try to fix this just by pushing bugs around in the code, you would need to call
+ # returnPackages and searchProvides and then apply the Nevra filters to those results.
+ pkgs = obj.searchNevra(**args)
+ if (command['action'] == "whatinstalled") and (not pkgs):
+ pkgs = obj.searchNevra(name=args['name'], arch=desired_arch)
+ else:
+ pats = [command['provides']]
+ pkgs = obj.returnPackages(patterns=pats)
+
+ if not pkgs:
+ # handles wildcards
+ pkgs = obj.searchProvides(command['provides'])
+
+ if not pkgs:
+ outpipe.write(command['provides'].split().pop(0)+' nil nil\n')
+ outpipe.flush()
+ else:
+ # make sure we picked the package with the highest version
+ pkgs = base.bestPackagesFromList(pkgs,single_name=True)
+ pkg = pkgs.pop(0)
+ outpipe.write("%(n)s %(e)s:%(v)s-%(r)s %(a)s\n" % { 'n': pkg.name, 'e': pkg.epoch, 'v': pkg.version, 'r': pkg.release, 'a': pkg.arch })
+ outpipe.flush()
+
+ # Reset any repos we were passed in enablerepo/disablerepo to the original state in enabled_repos
+ if 'repos' in command:
+ for repo in command['repos']:
+ if 'enable' in repo:
+ if base.repos.getRepo(repo['enable']) not in enabled_repos:
+ base.repos.disableRepo(repo['enable'])
+ if 'disable' in repo:
+ if base.repos.getRepo(repo['disable']) in enabled_repos:
+ base.repos.enableRepo(repo['disable'])
+
+# the design of this helper is that it should try to be 'brittle' and fail hard and exit in order
+# to keep process tables clean. additional error handling should probably be added to the retry loop
+# on the ruby side.
+def exit_handler(signal, frame):
+ if base is not None:
+ base.closeRpmDB()
+ sys.exit(0)
+
+def setup_exit_handler():
+ signal.signal(signal.SIGINT, exit_handler)
+ signal.signal(signal.SIGHUP, exit_handler)
+ signal.signal(signal.SIGPIPE, exit_handler)
+ signal.signal(signal.SIGQUIT, exit_handler)
+
+if len(sys.argv) < 3:
+ inpipe = sys.stdin
+ outpipe = sys.stdout
+else:
+ inpipe = os.fdopen(int(sys.argv[1]), "r")
+ outpipe = os.fdopen(int(sys.argv[2]), "w")
+
+try:
+ while 1:
+ # kill self if we get orphaned (tragic)
+ ppid = os.getppid()
+ if ppid == 1:
+ raise RuntimeError("orphaned")
+
+ setup_exit_handler()
+ line = inpipe.readline()
+
+ try:
+ command = json.loads(line)
+ except ValueError, e:
+ raise RuntimeError("bad json parse")
+
+ if command['action'] == "whatinstalled":
+ query(command)
+ elif command['action'] == "whatavailable":
+ query(command)
+ elif command['action'] == "versioncompare":
+ versioncompare(command['versions'])
+ elif command['action'] == "installonlypkgs":
+ install_only_packages(command['package'])
+ else:
+ raise RuntimeError("bad command")
+finally:
+ if base is not None:
+ base.closeRpmDB()
diff --git a/lib/chef/provider/package/zypper.rb b/lib/chef/provider/package/zypper.rb
index e20a7332f7..a1a433cbdf 100644
--- a/lib/chef/provider/package/zypper.rb
+++ b/lib/chef/provider/package/zypper.rb
@@ -1,9 +1,8 @@
-# -*- coding: utf-8 -*-
#
# Authors:: Adam Jacob (<adam@chef.io>)
# Ionuț Arțăriși (<iartarisi@suse.cz>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
-# Copyright 2013-2016, SUSE Linux GmbH
+# Copyright:: Copyright (c) Chef Software Inc.
+# Copyright:: 2013-2016, SUSE Linux GmbH
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,70 +18,119 @@
# limitations under the License.
#
-require "chef/provider/package"
-require "chef/resource/zypper_package"
+require_relative "../package"
+require_relative "../../resource/zypper_package"
class Chef
class Provider
class Package
class Zypper < Chef::Provider::Package
use_multipackage_api
+ allow_nils
provides :package, platform_family: "suse"
- provides :zypper_package, os: "linux"
+ provides :zypper_package
- def get_versions(package_name)
- candidate_version = current_version = nil
+ def load_current_resource
+ @current_resource = Chef::Resource::ZypperPackage.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
+ current_resource.version(get_current_versions)
+ current_resource
+ end
+
+ def install_package(name, version)
+ zypper_package("install", global_options, *options, "--auto-agree-with-licenses", allow_downgrade, name, version)
+ end
+
+ def upgrade_package(name, version)
+ # `zypper install` upgrades packages, we rely on the idempotency checks to get action :install behavior
+ install_package(name, version)
+ end
+
+ def remove_package(name, version)
+ zypper_package("remove", global_options, *options, name, version)
+ end
+
+ def purge_package(name, version)
+ zypper_package("remove", global_options, *options, "--clean-deps", name, version)
+ end
+
+ def lock_package(name, version)
+ zypper_package("addlock", global_options, *options, name, version)
+ end
+
+ def unlock_package(name, version)
+ zypper_package("removelock", global_options, *options, name, version)
+ end
+
+ private
+
+ def get_current_versions
+ package_name_array.each_with_index.map { |pkg, i| installed_version(i) }
+ end
+
+ def candidate_version
+ @candidate_version ||= package_name_array.each_with_index.map { |pkg, i| available_version(i) }
+ end
+
+ def resolve_current_version(package_name)
+ latest_version = current_version = nil
is_installed = false
- Chef::Log.debug("#{new_resource} checking zypper")
- status = shell_out_with_timeout!("zypper --non-interactive info #{package_name}")
+ logger.trace("#{new_resource} checking zypper")
+ status = shell_out!("zypper", "--non-interactive", "info", package_name)
status.stdout.each_line do |line|
case line
when /^Version *: (.+) *$/
- candidate_version = $1.strip
- Chef::Log.debug("#{new_resource} version #{candidate_version}")
- when /^Installed *: Yes *$/
+ latest_version = $1.strip
+ logger.trace("#{new_resource} version #{latest_version}")
+ when /^Installed *: Yes.*$/ # http://rubular.com/r/9StcAMjOn6
is_installed = true
- Chef::Log.debug("#{new_resource} is installed")
+ logger.trace("#{new_resource} is installed")
when /^Status *: out-of-date \(version (.+) installed\) *$/
current_version = $1.strip
- Chef::Log.debug("#{new_resource} out of date version #{current_version}")
+ logger.trace("#{new_resource} out of date version #{current_version}")
end
end
- current_version = candidate_version if is_installed
- { current_version: current_version, candidate_version: candidate_version }
+ current_version ||= latest_version if is_installed
+ current_version
end
- def versions
- @versions ||=
- begin
- raw_versions = package_name_array.map do |package_name|
- get_versions(package_name)
+ def resolve_available_version(package_name, new_version)
+ search_string = new_version.nil? ? package_name : "#{package_name}=#{new_version}"
+ so = shell_out!("zypper", "--non-interactive", "search", "-s", "--provides", "--match-exact", "--type=package", search_string)
+ so.stdout.each_line do |line|
+ if md = line.match(/^(\S*)\s+\|\s+(\S+)\s+\|\s+(\S+)\s+\|\s+(\S+)\s+\|\s+(\S+)\s+\|\s+(.*)$/)
+ (status, name, type, version, arch, repo) = [ md[1], md[2], md[3], md[4], md[5], md[6] ]
+ next if version == "Version" # header
+
+ # sometimes even though we request a specific version in the search string above and have match exact, we wind up
+ # with other versions in the output, particularly getting the installed version when downgrading.
+ if new_version
+ next unless version == new_version || version.start_with?("#{new_version}-")
end
- Hash[*package_name_array.zip(raw_versions).flatten]
- end
- end
- def get_candidate_versions
- package_name_array.map do |package_name|
- versions[package_name][:candidate_version]
+ return version
+ end
end
+ nil
end
- def get_current_versions
- package_name_array.map do |package_name|
- versions[package_name][:current_version]
- end
+ def available_version(index)
+ @available_version ||= []
+ @available_version[index] ||= resolve_available_version(package_name_array[index], safe_version_array[index])
+ @available_version[index]
end
- def load_current_resource
- @current_resource = Chef::Resource::ZypperPackage.new(new_resource.name)
- current_resource.package_name(new_resource.package_name)
-
- @candidate_version = get_candidate_versions
- current_resource.version(get_current_versions)
+ def installed_version(index)
+ @installed_version ||= []
+ @installed_version[index] ||= resolve_current_version(package_name_array[index])
+ @installed_version[index]
+ end
- current_resource
+ def zip(names, versions)
+ names.zip(versions).map do |n, v|
+ (v.nil? || v.empty?) ? n : "#{n}=#{v}"
+ end.compact
end
def zypper_version
@@ -90,53 +138,55 @@ class Chef
`zypper -V 2>&1`.scan(/\d+/).join(".").to_f
end
- def install_package(name, version)
- zypper_package("install --auto-agree-with-licenses", name, version)
+ def zypper_package(command, global_options, *options, names, versions)
+ zipped_names = zip(names, versions)
+ if zypper_version < 1.0
+ shell_out!("zypper", global_options, gpg_checks, command, *options, "-y", names)
+ else
+ shell_out!("zypper", global_options, "--non-interactive", gpg_checks, command, *options, zipped_names)
+ end
end
- def upgrade_package(name, version)
- # `zypper install` upgrades packages, we rely on the idempotency checks to get action :install behavior
- install_package(name, version)
+ def gpg_checks
+ "--no-gpg-checks" unless new_resource.gpg_check
end
- def remove_package(name, version)
- zypper_package("remove", name, version)
+ def allow_downgrade
+ "--oldpackage" if new_resource.allow_downgrade
end
- def purge_package(name, version)
- zypper_package("remove --clean-deps", name, version)
+ def global_options
+ new_resource.global_options
end
- private
+ def packages_all_locked?(names, versions)
+ names.all? { |n| locked_packages.include? n }
+ end
- def zip(names, versions)
- names.zip(versions).map do |n, v|
- (v.nil? || v.empty?) ? n : "#{n}=#{v}"
- end
+ def packages_all_unlocked?(names, versions)
+ names.all? { |n| !locked_packages.include? n }
end
- def zypper_package(command, names, versions)
- zipped_names = zip(names, versions)
- if zypper_version < 1.0
- shell_out_with_timeout!(a_to_s("zypper", gpg_checks, command, "-y", names))
- else
- shell_out_with_timeout!(a_to_s("zypper --non-interactive", gpg_checks, command, zipped_names))
- end
+ def locked_packages
+ @locked_packages ||=
+ begin
+ locked = shell_out!("zypper", "locks")
+ locked.stdout.each_line.map do |line|
+ line.split("|").shift(2).last.strip
+ end
+ end
end
- def gpg_checks()
- case Chef::Config[:zypper_check_gpg]
- when true
- ""
- when false
- "--no-gpg-checks"
- when nil
- Chef::Log.warn("Chef::Config[:zypper_check_gpg] was not set. " +
- "All packages will be installed without gpg signature checks. " +
- "This is a security hazard.")
- "--no-gpg-checks"
+ def safe_version_array
+ if new_resource.version.is_a?(Array)
+ new_resource.version
+ elsif new_resource.version.nil?
+ package_name_array.map { nil }
+ else
+ [ new_resource.version ]
end
end
+
end
end
end
diff --git a/lib/chef/provider/powershell_script.rb b/lib/chef/provider/powershell_script.rb
index ab85ec35ac..8360dd873b 100644
--- a/lib/chef/provider/powershell_script.rb
+++ b/lib/chef/provider/powershell_script.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Edwards (<adamed@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,87 +16,89 @@
# limitations under the License.
#
-require "chef/platform/query_helpers"
-require "chef/provider/windows_script"
+require_relative "../platform/query_helpers"
+require_relative "windows_script"
class Chef
class Provider
class PowershellScript < Chef::Provider::WindowsScript
+ # FIXME: use composition not inheritance
- provides :powershell_script, os: "windows"
+ provides :powershell_script
- def initialize(new_resource, run_context)
- super(new_resource, run_context, ".ps1")
- add_exit_status_wrapper
- end
-
- def action_run
+ action :run do
validate_script_syntax!
- super
+ 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)
+ # Set InputFormat to None as PowerShell will hang if STDIN is redirected
+ # http://connect.microsoft.com/PowerShell/feedback/details/572313/powershell-exe-can-hang-if-stdin-is-redirected
+ DEFAULT_FLAGS = "-NoLogo -NonInteractive -NoProfile -ExecutionPolicy Bypass -InputFormat None".freeze
+ def command
# 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
+ [
+ %Q{"#{interpreter_path}"},
+ DEFAULT_FLAGS,
+ new_resource.flags,
+ %Q{-File "#{script_file_path}"},
+ ].join(" ")
end
- def flags
- interpreter_flags = [*default_interpreter_flags].join(" ")
+ protected
- if ! (@new_resource.flags.nil?)
- interpreter_flags = [@new_resource.flags, interpreter_flags].join(" ")
+ def interpreter_path
+ # Powershell.exe is always in "v1.0" folder (for backwards compatibility)
+ # pwsh is the other interpreter and we will assume that it is on the path.
+ # It will exist in different folders depending on the installed version.
+ # There can also be multiple versions installed. Depending on how it was installed,
+ # there might be a registry entry pointing to the installation path. The key will
+ # differ depending on version and architecture. It seems best to let the PATH
+ # determine the file path to use since that will provide the same pwsh.exe one
+ # would invoke from any shell.
+ if interpreter == "powershell"
+ Chef::Util::PathHelper.join(basepath, "WindowsPowerShell", "v1.0", "#{interpreter}.exe")
+ else
+ interpreter
end
-
- interpreter_flags
end
- protected
-
- # 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")
+ def code
+ code = wrapper_script
+ logger.trace("powershell_script provider called with script code:\n\n#{new_resource.code}\n")
+ logger.trace("powershell_script provider will execute transformed code:\n\n#{code}\n")
+ code
end
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_code_wrapped_in_powershell_script_block = <<~EOH
+ {
+ #{new_resource.code}
+ }
+ EOH
user_script_file.puts user_code_wrapped_in_powershell_script_block
# A .close or explicit .flush required to ensure the file is
# written to the file system at this point, which is required since
# the intent is to execute the code just written to it.
user_script_file.close
- validation_command = "\"#{interpreter}\" #{interpreter_arguments} -Command \". '#{user_script_file.path}'\""
+ validation_command = [
+ %Q{"#{interpreter_path}"},
+ DEFAULT_FLAGS,
+ new_resource.flags,
+ %Q{-Command ". '#{user_script_file.path}'"},
+ ].join(" ")
# Note that other script providers like bash allow syntax errors
# to be suppressed by setting 'returns' to a value that the
@@ -110,106 +112,99 @@ EOH
# 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] })
+ shell_out!(validation_command, returns: [0])
end
end
end
- 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 #{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",
- ]
- end
-
+ # Process exit codes are strange with PowerShell and require
+ # special handling to cover common use cases.
# 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
+ # 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
+ # 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
-
-# LASTEXITCODE can be uninitialized -- make it explictly 0
-# to avoid incorrect detection of failure (non-zero) codes
-$global:LASTEXITCODE = 0
-
-# 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
+ <<~EOH
+ # Chef Client wrapper for powershell_script resources
+
+ # In rare cases, such as when PowerShell is executed
+ # as an alternate user, the new-variable cmdlet is not
+ # available, so import it just in case
+ if ( get-module -ListAvailable Microsoft.PowerShell.Utility )
+ {
+ Import-Module Microsoft.PowerShell.Utility
+ }
+
+ # LASTEXITCODE can be uninitialized -- make it explicitly 0
+ # to avoid incorrect detection of failure (non-zero) codes
+ $global:LASTEXITCODE = 0
+
+ # 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
+ def script_extension
+ ".ps1"
+ end
end
end
end
diff --git a/lib/chef/provider/reboot.rb b/lib/chef/provider/reboot.rb
deleted file mode 100644
index 34eee9236d..0000000000
--- a/lib/chef/provider/reboot.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-#
-# Author:: Chris Doherty <cdoherty@chef.io>)
-# Copyright:: Copyright 2014-2016, Chef, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require "chef/log"
-require "chef/provider"
-
-class Chef
- class Provider
- class Reboot < Chef::Provider
- provides :reboot
-
- def whyrun_supported?
- true
- end
-
- def load_current_resource
- @current_resource ||= Chef::Resource::Reboot.new(@new_resource.name)
- @current_resource.reason(@new_resource.reason)
- @current_resource.delay_mins(@new_resource.delay_mins)
- @current_resource
- end
-
- def request_reboot
- node.run_context.request_reboot(
- :delay_mins => @new_resource.delay_mins,
- :reason => @new_resource.reason,
- :timestamp => Time.now,
- :requested_by => @new_resource.name
- )
- end
-
- def action_request_reboot
- converge_by("request a system reboot to occur if the run succeeds") do
- Chef::Log.warn "Reboot requested:'#{@new_resource.name}'"
- request_reboot
- end
- end
-
- def action_reboot_now
- converge_by("rebooting the system immediately") do
- Chef::Log.warn "Rebooting system immediately, requested by '#{@new_resource.name}'"
- request_reboot
- throw :end_client_run_early
- end
- end
-
- def action_cancel
- converge_by("cancel any existing end-of-run reboot request") do
- Chef::Log.warn "Reboot canceled: '#{@new_resource.name}'"
- node.run_context.cancel_reboot
- end
- end
- end
- end
-end
diff --git a/lib/chef/provider/registry_key.rb b/lib/chef/provider/registry_key.rb
index 5e8dbe9bd8..316a2a1081 100644
--- a/lib/chef/provider/registry_key.rb
+++ b/lib/chef/provider/registry_key.rb
@@ -2,7 +2,7 @@
# Author:: Prajakta Purohit (<prajakta@chef.io>)
# Author:: Lamont Granquist (<lamont@chef.io>)
#
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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.
@@ -17,15 +17,15 @@
# 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_relative "../config"
+require_relative "../log"
+require_relative "../resource/file"
+require_relative "../mixin/checksum"
+require_relative "../provider"
+require "etc" unless defined?(Etc)
+require "fileutils" unless defined?(FileUtils)
+require_relative "../scan_access_control"
+require_relative "../win32/registry"
class Chef
@@ -35,31 +35,29 @@ class Chef
include Chef::Mixin::Checksum
- def whyrun_supported?
- true
- end
+ WORD_TYPES = %i{dword dword_big_endian qword}.freeze
def running_on_windows!
- unless Chef::Platform.windows?
+ unless ChefUtils.windows?
raise Chef::Exceptions::Win32NotWindows, "Attempt to manipulate the windows registry on a non-windows node"
end
end
def load_current_resource
running_on_windows!
- @current_resource ||= Chef::Resource::RegistryKey.new(@new_resource.key, run_context)
- @current_resource.key(@new_resource.key)
- @current_resource.architecture(@new_resource.architecture)
- @current_resource.recursive(@new_resource.recursive)
- if registry.key_exists?(@new_resource.key)
- @current_resource.values(registry.get_values(@new_resource.key))
- end
- values_to_hash(@current_resource.unscrubbed_values)
- @current_resource
+ @current_resource ||= Chef::Resource::RegistryKey.new(new_resource.key, run_context)
+ current_resource.key(new_resource.key)
+ current_resource.architecture(new_resource.architecture)
+ current_resource.recursive(new_resource.recursive)
+ if registry.key_exists?(new_resource.key)
+ current_resource.values(registry.get_values(new_resource.key))
+ end
+ values_to_hash(current_resource.unscrubbed_values)
+ current_resource
end
def registry
- @registry ||= Chef::Win32::Registry.new(@run_context, @new_resource.architecture)
+ @registry ||= Chef::Win32::Registry.new(@run_context, new_resource.architecture)
end
def values_to_hash(values)
@@ -70,85 +68,129 @@ class Chef
end
end
+ def key_missing?(values, name)
+ values.each do |v|
+ return true unless v.key?(name)
+ end
+ false
+ end
+
def define_resource_requirements
requirements.assert(:create, :create_if_missing, :delete, :delete_key) do |a|
- a.assertion { registry.hive_exists?(@new_resource.key) }
- a.failure_message(Chef::Exceptions::Win32RegHiveMissing, "Hive #{@new_resource.key.split("\\").shift} does not exist")
+ a.assertion { registry.hive_exists?(new_resource.key) }
+ a.failure_message(Chef::Exceptions::Win32RegHiveMissing, "Hive #{new_resource.key.split('\\').shift} does not exist")
end
+
requirements.assert(:create) do |a|
- a.assertion { registry.key_exists?(@new_resource.key) }
- a.whyrun("Key #{@new_resource.key} does not exist. Unless it would have been created before, attempt to modify its values would fail.")
+ a.assertion { registry.key_exists?(new_resource.key) }
+ a.whyrun("Key #{new_resource.key} does not exist. Unless it would have been created before, attempt to modify its values would fail.")
end
+
requirements.assert(:create, :create_if_missing) do |a|
- #If keys missing in the path and recursive == false
- a.assertion { !registry.keys_missing?(@current_resource.key) || @new_resource.recursive }
+ # If keys missing in the path and recursive == false
+ a.assertion { !registry.keys_missing?(current_resource.key) || new_resource.recursive }
a.failure_message(Chef::Exceptions::Win32RegNoRecursive, "Intermediate keys missing but recursive is set to false")
- a.whyrun("Intermediate keys in #{@new_resource.key} do not exist. Unless they would have been created earlier, attempt to modify them would fail.")
+ a.whyrun("Intermediate keys in #{new_resource.key} do not exist. Unless they would have been created earlier, attempt to modify them would fail.")
end
+
requirements.assert(:delete_key) do |a|
- #If key to be deleted has subkeys but recurssive == false
- a.assertion { !registry.key_exists?(@new_resource.key) || !registry.has_subkeys?(@new_resource.key) || @new_resource.recursive }
- a.failure_message(Chef::Exceptions::Win32RegNoRecursive, "#{@new_resource.key} has subkeys but recursive is set to false.")
- a.whyrun("#{@current_resource.key} has subkeys, but recursive is set to false. attempt to delete would fails unless subkeys were deleted prior to this action.")
+ # If key to be deleted has subkeys but recursive == false
+ a.assertion { !registry.key_exists?(new_resource.key) || !registry.has_subkeys?(new_resource.key) || new_resource.recursive }
+ a.failure_message(Chef::Exceptions::Win32RegNoRecursive, "#{new_resource.key} has subkeys but recursive is set to false.")
+ a.whyrun("#{current_resource.key} has subkeys, but recursive is set to false. attempt to delete would fails unless subkeys were deleted prior to this action.")
+ end
+
+ requirements.assert(:create, :create_if_missing) do |a|
+ # If type key missing in the RegistryKey values hash
+ a.assertion { !key_missing?(new_resource.values, :type) }
+ a.failure_message(Chef::Exceptions::RegKeyValuesTypeMissing, "Missing type key in RegistryKey values hash")
+ a.whyrun("Type key does not exist. Attempt would fail unless the complete values hash containing all the keys does not exist for registry_key resource's create action.")
+ end
+
+ requirements.assert(:create, :create_if_missing) do |a|
+ # If data key missing in the RegistryKey values hash
+ a.assertion { !key_missing?(new_resource.values, :data) }
+ a.failure_message(Chef::Exceptions::RegKeyValuesDataMissing, "Missing data key in RegistryKey values hash")
+ a.whyrun("Data key does not exist. Attempt would fail unless the complete values hash containing all the keys does not exist for registry_key resource's create action.")
end
end
- def action_create
- unless registry.key_exists?(@current_resource.key)
- converge_by("create key #{@new_resource.key}") do
- registry.create_key(@new_resource.key, @new_resource.recursive)
+ action :create do
+ unless registry.key_exists?(current_resource.key)
+ converge_by("create key #{new_resource.key}") do
+ registry.create_key(new_resource.key, new_resource.recursive)
end
end
- @new_resource.unscrubbed_values.each do |value|
- if @name_hash.has_key?(value[:name].downcase)
+ new_resource.unscrubbed_values.each do |value|
+ if @name_hash.key?(value[:name].downcase)
current_value = @name_hash[value[:name].downcase]
- if [:dword, :dword_big_endian, :qword].include? value[:type]
- value[:data] = value[:data].to_i
- end
+ value[:data] = value[:data].to_i if WORD_TYPES.include?(value[:type])
+
unless current_value[:type] == value[:type] && current_value[:data] == value[:data]
- converge_by("set value #{value}") do
- registry.set_value(@new_resource.key, value)
+ converge_by_value = if new_resource.sensitive
+ value.merge(data: "*sensitive value suppressed*")
+ else
+ value
+ end
+
+ converge_by("set value #{converge_by_value}") do
+ registry.set_value(new_resource.key, value)
end
end
else
- converge_by("set value #{value}") do
- registry.set_value(@new_resource.key, value)
+ converge_by_value = if new_resource.sensitive
+ value.merge(data: "*sensitive value suppressed*")
+ else
+ value
+ end
+
+ converge_by("set value #{converge_by_value}") do
+ registry.set_value(new_resource.key, value)
end
end
end
end
- def action_create_if_missing
- unless registry.key_exists?(@new_resource.key)
- converge_by("create key #{@new_resource.key}") do
- registry.create_key(@new_resource.key, @new_resource.recursive)
+ action :create_if_missing do
+ unless registry.key_exists?(new_resource.key)
+ converge_by("create key #{new_resource.key}") do
+ registry.create_key(new_resource.key, new_resource.recursive)
end
end
- @new_resource.unscrubbed_values.each do |value|
- unless @name_hash.has_key?(value[:name].downcase)
- converge_by("create value #{value}") do
- registry.set_value(@new_resource.key, value)
+ new_resource.unscrubbed_values.each do |value|
+ unless @name_hash.key?(value[:name].downcase)
+ converge_by_value = if new_resource.sensitive
+ value.merge(data: "*sensitive value suppressed*")
+ else
+ value
+ end
+
+ converge_by("create value #{converge_by_value}") do
+ registry.set_value(new_resource.key, value)
end
end
end
end
- def action_delete
- if registry.key_exists?(@new_resource.key)
- @new_resource.unscrubbed_values.each do |value|
- if @name_hash.has_key?(value[:name].downcase)
- converge_by("delete value #{value}") do
- registry.delete_value(@new_resource.key, value)
+ action :delete do
+ if registry.key_exists?(new_resource.key)
+ new_resource.unscrubbed_values.each do |value|
+ if @name_hash.key?(value[:name].downcase)
+ converge_by_value = value
+ converge_by_value[:data] = "*sensitive value suppressed*" if new_resource.sensitive
+
+ converge_by("delete value #{converge_by_value}") do
+ registry.delete_value(new_resource.key, value)
end
end
end
end
end
- def action_delete_key
- if registry.key_exists?(@new_resource.key)
- converge_by("delete key #{@new_resource.key}") do
- registry.delete_key(@new_resource.key, @new_resource.recursive)
+ action :delete_key do
+ if registry.key_exists?(new_resource.key)
+ converge_by("delete key #{new_resource.key}") do
+ registry.delete_key(new_resource.key, new_resource.recursive)
end
end
end
diff --git a/lib/chef/provider/remote_directory.rb b/lib/chef/provider/remote_directory.rb
index 15b71c43bd..0c2cce4026 100644
--- a/lib/chef/provider/remote_directory.rb
+++ b/lib/chef/provider/remote_directory.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,17 +16,15 @@
# limitations under the License.
#
-require "chef/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_relative "directory"
+require_relative "../resource/file"
+require_relative "../resource/directory"
+require_relative "../resource/cookbook_file"
+require_relative "../mixin/file_class"
+require_relative "../platform/query_helpers"
+require_relative "../util/path_helper"
-require "forwardable"
+require "forwardable" unless defined?(Forwardable)
class Chef
class Provider
@@ -36,9 +34,9 @@ class Chef
provides :remote_directory
- 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
+ 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.
#
@@ -49,8 +47,6 @@ class Chef
!!@overwrite
end
- attr_accessor :managed_files
-
# Hash containing keys of the paths for all the files that we sync, plus all their
# parent directories.
#
@@ -62,8 +58,8 @@ class Chef
# Handle action :create.
#
- def action_create
- super
+ action :create do
+ super()
# Transfer files
files_to_transfer.each do |cookbook_file_relative_path|
@@ -77,7 +73,7 @@ class Chef
# Handle action :create_if_missing.
#
- def action_create_if_missing
+ action :create_if_missing do
# if this action is called, ignore the existing overwrite flag
@overwrite = false
action_create
@@ -106,7 +102,7 @@ class Chef
if purge
Dir.glob(::File.join(Chef::Util::PathHelper.escape_glob_dir(path), "**", "*"), ::File::FNM_DOTMATCH).sort!.reverse!.each do |file|
# skip '.' and '..'
- next if [".", ".."].include?(Pathname.new(file).basename().to_s)
+ 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)
@@ -115,7 +111,7 @@ class Chef
next if managed_files.include?(file)
if ::File.directory?(file)
- if !Chef::Platform.windows? && file_class.symlink?(file.dup)
+ if !ChefUtils.windows? && file_class.symlink?(file.dup)
# Unix treats dir symlinks as files
purge_file(file)
else
@@ -151,11 +147,11 @@ class Chef
new_resource.updated_by_last_action(true) if res.updated?
end
- # Get the files to tranfer. This returns files in lexicographical sort order.
+ # Get the files to transfer. 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
+ # @return [Array<String>] The list of files to transfer
# @api private
#
def files_to_transfer
@@ -212,7 +208,7 @@ class Chef
# Set the sensitivity level
res.sensitive(new_resource.sensitive)
res.source(::File.join(source, relative_source_path))
- if Chef::Platform.windows? && files_rights
+ if ChefUtils.windows? && files_rights
files_rights.each_pair do |permission, *args|
res.rights(permission, *args)
end
@@ -248,8 +244,8 @@ class Chef
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;
+ if ChefUtils.windows? && rights
+ # rights are only meant to be applied to the most top-level directory;
# Windows will handle inheritance.
if dir == path
rights.each do |r|
@@ -268,16 +264,6 @@ class Chef
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 9207e62ac6..8c366adc10 100644
--- a/lib/chef/provider/remote_file.rb
+++ b/lib/chef/provider/remote_file.rb
@@ -1,7 +1,7 @@
#
# Author:: Jesse Campbell (<hikeit@gmail.com>)
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,34 +17,48 @@
# limitations under the License.
#
-require "chef/provider/file"
-require "chef/deprecation/provider/remote_file"
-require "chef/deprecation/warnings"
+require_relative "file"
class Chef
class Provider
class RemoteFile < Chef::Provider::File
provides :remote_file
- extend Chef::Deprecation::Warnings
- include Chef::Deprecation::Provider::RemoteFile
- add_deprecation_warnings_for(Chef::Deprecation::Provider::RemoteFile.instance_methods)
-
def initialize(new_resource, run_context)
@content_class = Chef::Provider::RemoteFile::Content
super
end
+ def define_resource_requirements
+ [ new_resource.remote_user, new_resource.remote_domain,
+ new_resource.remote_password ].each do |prop|
+ requirements.assert(:all_actions) do |a|
+ a.assertion do
+ if prop
+ windows?
+ else
+ true
+ end
+ end
+ a.failure_message Chef::Exceptions::UnsupportedPlatform, "'remote_user', 'remote_domain' and 'remote_password' properties are supported only for Windows platform"
+ a.whyrun("Assuming that the platform is Windows while passing 'remote_user', 'remote_domain' and 'remote_password' properties")
+ end
+ end
+
+ super
+ end
+
def load_current_resource
- @current_resource = Chef::Resource::RemoteFile.new(@new_resource.name)
+ @current_resource = Chef::Resource::RemoteFile.new(new_resource.name)
super
end
private
def managing_content?
- return true if @new_resource.checksum
- return true if !@new_resource.source.nil? && @action != :create_if_missing
+ return true if new_resource.checksum
+ return true if !new_resource.source.nil? && @action != :create_if_missing
+
false
end
diff --git a/lib/chef/provider/remote_file/cache_control_data.rb b/lib/chef/provider/remote_file/cache_control_data.rb
index 8d7de5c370..b8c7483f21 100644
--- a/lib/chef/provider/remote_file/cache_control_data.rb
+++ b/lib/chef/provider/remote_file/cache_control_data.rb
@@ -3,7 +3,7 @@
# Author:: Jesse Campbell (<hikeit@gmail.com>)
# Author:: Lamont Granquist (<lamont@chef.io>)
# Copyright:: Copyright 2013-2016, Jesse Campbell
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -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" unless defined?(StringIO)
+require_relative "../../file_cache"
+require_relative "../../json_compat"
+require_relative "../../digester"
+require_relative "../../exceptions"
class Chef
class Provider
@@ -146,14 +146,14 @@ class Chef
def load_json_data
path = sanitized_cache_file_path(sanitized_cache_file_basename)
- if Chef::FileCache.has_key?(path)
+ if Chef::FileCache.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)
+ if Chef::FileCache.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::Log.trace("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)
diff --git a/lib/chef/provider/remote_file/content.rb b/lib/chef/provider/remote_file/content.rb
index e44096428b..665da47e8d 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@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,10 +17,13 @@
# limitations under the License.
#
-require "uri"
-require "tempfile"
-require "chef/file_content_management/content_base"
-require "chef/mixin/uris"
+require "uri" unless defined?(URI)
+require "tempfile" unless defined?(Tempfile)
+require_relative "../../file_content_management/content_base"
+require_relative "../../mixin/uris"
+module Net
+ autoload :FTPError, "net/ftp"
+end
class Chef
class Provider
@@ -32,10 +35,10 @@ class Chef
include Chef::Mixin::Uris
def file_for_provider
- Chef::Log.debug("#{@new_resource} checking for changes")
+ logger.trace("#{@new_resource} checking for changes")
if current_resource_matches_target_checksum?
- Chef::Log.debug("#{@new_resource} checksum matches target checksum (#{@new_resource.checksum}) - not updating")
+ logger.trace("#{@new_resource} checksum matches target checksum (#{@new_resource.checksum}) - not updating")
else
sources = @new_resource.source
raw_file = try_multiple_sources(sources)
@@ -54,10 +57,10 @@ class Chef
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}")
+ rescue SocketError, Errno::ECONNREFUSED, Errno::ENOENT, Errno::EACCES, Timeout::Error, Net::HTTPClientException, Net::HTTPFatalError, Net::FTPError, Errno::ETIMEDOUT => e
+ logger.warn("#{@new_resource} cannot be downloaded from #{source}: #{e}")
if source = sources.shift
- Chef::Log.info("#{@new_resource} trying to download from another mirror")
+ logger.info("#{@new_resource} trying to download from another mirror")
retry
else
raise e
diff --git a/lib/chef/provider/remote_file/fetcher.rb b/lib/chef/provider/remote_file/fetcher.rb
index 563d135d6a..dc6de6d223 100644
--- a/lib/chef/provider/remote_file/fetcher.rb
+++ b/lib/chef/provider/remote_file/fetcher.rb
@@ -1,7 +1,7 @@
#
# Author:: Jesse Campbell (<hikeit@gmail.com>)
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,6 +24,10 @@ class Chef
def self.for_resource(uri, new_resource, current_resource)
if network_share?(uri)
+ unless ChefUtils.windows?
+ raise Exceptions::UnsupportedPlatform, "Fetching the file on a network share is supported only on the Windows platform. Please change your source: #{uri}"
+ end
+
Chef::Provider::RemoteFile::NetworkFile.new(uri, new_resource, current_resource)
else
case uri.scheme
@@ -45,7 +49,7 @@ class Chef
def self.network_share?(source)
case source
when String
- !!(%r{\A\\\\[A-Za-z0-9+\-\.]+} =~ source)
+ !!(/\A\\\\[A-Za-z0-9+\-\.]+/ =~ source)
else
false
end
diff --git a/lib/chef/provider/remote_file/ftp.rb b/lib/chef/provider/remote_file/ftp.rb
index b382c20c31..44a6d1c6e8 100644
--- a/lib/chef/provider/remote_file/ftp.rb
+++ b/lib/chef/provider/remote_file/ftp.rb
@@ -16,11 +16,14 @@
# limitations under the License.
#
-require "uri"
-require "tempfile"
-require "net/ftp"
-require "chef/provider/remote_file"
-require "chef/file_content_management/tempfile"
+autoload :URI, "uri"
+autoload :CGI, "cgi"
+autoload :Tempfile, "tempfile"
+module Net
+ autoload :FTP, "net/ftp"
+end
+require_relative "../remote_file"
+require_relative "../../file_content_management/tempfile"
class Chef
class Provider
@@ -57,7 +60,7 @@ class Chef
def user
if uri.userinfo
- URI.unescape(uri.user)
+ CGI.unescape(uri.user)
else
"anonymous"
end
@@ -65,7 +68,7 @@ class Chef
def pass
if uri.userinfo
- URI.unescape(uri.password)
+ CGI.unescape(uri.password)
else
nil
end
diff --git a/lib/chef/provider/remote_file/http.rb b/lib/chef/provider/remote_file/http.rb
index ad044f9e3c..ef2461848d 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_relative "../../http/simple"
+require_relative "../../digester"
+require_relative "../remote_file"
+require_relative "cache_control_data"
class Chef
class Provider
@@ -31,12 +31,14 @@ class Chef
attr_reader :uri
attr_reader :new_resource
attr_reader :current_resource
+ attr_reader :logger
# Parse the uri into instance variables
- def initialize(uri, new_resource, current_resource)
+ def initialize(uri, new_resource, current_resource, logger = Chef::Log.with_child)
@uri = uri
@new_resource = new_resource
@current_resource = current_resource
+ @logger = logger
end
def events
@@ -55,22 +57,28 @@ class Chef
if (etag = cache_control_data.etag) && want_etag_cache_control?
cache_control_headers["if-none-match"] = etag
end
- Chef::Log.debug("Cache control headers: #{cache_control_headers.inspect}")
+ logger.trace("Cache control headers: #{cache_control_headers.inspect}")
cache_control_headers
end
def fetch
http = Chef::HTTP::Simple.new(uri, http_client_opts)
+ orig_tempfile = Chef::FileContentManagement::Tempfile.new(@new_resource).tempfile
if want_progress?
- tempfile = http.streaming_request_with_progress(uri, headers) do |size, total|
+ tempfile = http.streaming_request_with_progress(uri, headers, orig_tempfile) do |size, total|
events.resource_update_progress(new_resource, size, total, progress_interval)
end
else
- tempfile = http.streaming_request(uri, headers)
+ tempfile = http.streaming_request(uri, headers, orig_tempfile)
end
if tempfile
update_cache_control_data(tempfile, http.last_response)
tempfile.close
+ else
+ # cache_control shows the file is unchanged, so we got back nil from the streaming_request above, and it is
+ # now our responsibility to unlink the tempfile we created
+ orig_tempfile.close
+ orig_tempfile.unlink
end
tempfile
end
@@ -122,10 +130,13 @@ class Chef
# which tricks Chef::REST into decompressing the response body. In this
# case you'd end up with a tar archive (no gzip) named, e.g., foo.tgz,
# which is not what you wanted.
- if uri.to_s =~ /gz$/
- Chef::Log.debug("Turning gzip compression off due to filename ending in gz")
+ if /gz$/.match?(uri.to_s)
+ logger.trace("Turning gzip compression off due to filename ending in gz")
opts[:disable_gzip] = true
end
+ if new_resource.ssl_verify_mode
+ opts[:ssl_verify_mode] = new_resource.ssl_verify_mode
+ end
opts
end
diff --git a/lib/chef/provider/remote_file/local_file.rb b/lib/chef/provider/remote_file/local_file.rb
index 613db02337..c68c4eecd5 100644
--- a/lib/chef/provider/remote_file/local_file.rb
+++ b/lib/chef/provider/remote_file/local_file.rb
@@ -16,9 +16,10 @@
# limitations under the License.
#
-require "uri"
-require "tempfile"
-require "chef/provider/remote_file"
+require "uri" unless defined?(URI)
+require "cgi" unless defined?(CGI)
+require "tempfile" unless defined?(Tempfile)
+require_relative "../remote_file"
class Chef
class Provider
@@ -35,20 +36,20 @@ class Chef
# 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')
+ path.gsub(%r{^/([a-zA-Z]:)}, '\1')
end
def source_path
@source_path ||= begin
- path = URI.unescape(uri.path)
- Chef::Platform.windows? ? fix_windows_path(path) : path
+ path = CGI.unescape(uri.path)
+ ChefUtils.windows? ? fix_windows_path(path) : path
end
end
# Fetches the file at uri, returning a Tempfile-like File handle
def fetch
tempfile = Chef::FileContentManagement::Tempfile.new(new_resource).tempfile
- Chef::Log.debug("#{new_resource} staging #{source_path} to #{tempfile.path}")
+ Chef::Log.trace("#{new_resource} staging #{source_path} to #{tempfile.path}")
FileUtils.cp(source_path, tempfile.path)
tempfile.close if tempfile
tempfile
diff --git a/lib/chef/provider/remote_file/network_file.rb b/lib/chef/provider/remote_file/network_file.rb
index 44046132a9..b08aeb55cc 100644
--- a/lib/chef/provider/remote_file/network_file.rb
+++ b/lib/chef/provider/remote_file/network_file.rb
@@ -16,17 +16,21 @@
# limitations under the License.
#
-require "uri"
-require "tempfile"
-require "chef/provider/remote_file"
+require "uri" unless defined?(URI)
+require "tempfile" unless defined?(Tempfile)
+require_relative "../remote_file"
+require_relative "../../mixin/user_context"
class Chef
class Provider
class RemoteFile
class NetworkFile
+ include Chef::Mixin::UserContext
attr_reader :new_resource
+ TRANSFER_CHUNK_SIZE = 1048576
+
def initialize(source, new_resource, current_resource)
@new_resource = new_resource
@source = source
@@ -35,13 +39,22 @@ class Chef
# 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
+ begin
+ tempfile = Chef::FileContentManagement::Tempfile.new(new_resource).tempfile
+ Chef::Log.trace("#{new_resource} staging #{@source} to #{tempfile.path}")
+
+ with_user_context(new_resource.remote_user, new_resource.remote_password, new_resource.remote_domain, new_resource.authentication) do
+ ::File.open(@source, "rb") do |remote_file|
+ while data = remote_file.read(TRANSFER_CHUNK_SIZE)
+ tempfile.write(data)
+ end
+ end
+ end
+ ensure
+ tempfile.close if tempfile
+ end
tempfile
end
-
end
end
end
diff --git a/lib/chef/provider/remote_file/sftp.rb b/lib/chef/provider/remote_file/sftp.rb
index 21c5c4ca04..be2a34fc54 100644
--- a/lib/chef/provider/remote_file/sftp.rb
+++ b/lib/chef/provider/remote_file/sftp.rb
@@ -16,11 +16,14 @@
# limitations under the License.
#
-require "uri"
-require "tempfile"
-require "net/sftp"
-require "chef/provider/remote_file"
-require "chef/file_content_management/tempfile"
+autoload :URI, "uri"
+autoload :CGI, "cgi"
+autoload :Tempfile, "tempfile"
+module Net
+ autoload :SFTP, "net/sftp"
+end
+require_relative "../remote_file"
+require_relative "../../file_content_management/tempfile"
class Chef
class Provider
@@ -47,7 +50,7 @@ class Chef
end
def user
- URI.unescape(uri.user)
+ CGI.unescape(uri.user)
end
def fetch
@@ -58,11 +61,11 @@ class Chef
def sftp
host = port ? "#{hostname}:#{port}" : hostname
- @sftp ||= Net::SFTP.start(host, user, :password => pass)
+ @sftp ||= Net::SFTP.start(host, user, password: pass)
end
def pass
- URI.unescape(uri.password)
+ CGI.unescape(uri.password)
end
def validate_path!
diff --git a/lib/chef/provider/resource_update.rb b/lib/chef/provider/resource_update.rb
index e069a8201c..d3c674dd22 100644
--- a/lib/chef/provider/resource_update.rb
+++ b/lib/chef/provider/resource_update.rb
@@ -31,7 +31,7 @@ class Chef
attr_accessor :type
attr_accessor :name
- attr_accessor :duration #ms
+ attr_accessor :duration # ms
attr_accessor :status
attr_accessor :initial_state
attr_accessor :final_state
diff --git a/lib/chef/provider/route.rb b/lib/chef/provider/route.rb
index 64c89aac6d..614d56aa22 100644
--- a/lib/chef/provider/route.rb
+++ b/lib/chef/provider/route.rb
@@ -1,6 +1,7 @@
#
# Author:: Bryan McLellan (btm@loftninjas.org), Jesse Nelson (spheromak@gmail.com)
# Copyright:: Copyright 2009-2016, Bryan McLellan
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,214 +17,232 @@
# limitations under the License.
#
-require "chef/log"
-require "chef/mixin/command"
-require "chef/provider"
-require "ipaddr"
-
-class Chef::Provider::Route < Chef::Provider
- include Chef::Mixin::Command
-
- provides :route
-
- 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" }
-
- def hex2ip(hex_data)
- # Cleanup hex data
- 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 }
-
- # Validate IP
- ip = octets.join(".")
- begin
- IPAddr.new(ip, Socket::AF_INET).to_s
- rescue ArgumentError
- Chef::Log.debug("Invalid IP address data: hex=#{hex_ip}, ip=#{ip}")
- return nil
- end
- end
-
- def whyrun_supported?
- true
- end
-
- def load_current_resource
- self.is_running = false
+require_relative "../log"
+require_relative "../provider"
+autoload :IPAddr, "ipaddr"
+
+class Chef
+ class Provider
+ class Route < Chef::Provider
+
+ provides :route
+
+ 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" }.freeze
+
+ def hex2ip(hex_data)
+ # Cleanup hex data
+ 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 }
+
+ # Validate IP
+ ip = octets.join(".")
+ begin
+ IPAddr.new(ip, Socket::AF_INET).to_s
+ rescue ArgumentError
+ logger.trace("Invalid IP address data: hex=#{hex_ip}, ip=#{ip}")
+ nil
+ end
+ end
- # cidr or quad dot mask
- if @new_resource.netmask
- new_ip = IPAddr.new("#{@new_resource.target}/#{@new_resource.netmask}")
- else
- new_ip = IPAddr.new(@new_resource.target)
- end
+ def load_current_resource
+ self.is_running = false
+
+ # cidr or quad dot mask
+ new_ip = if new_resource.target == "default"
+ IPAddr.new(new_resource.gateway)
+ elsif new_resource.netmask
+ IPAddr.new("#{new_resource.target}/#{new_resource.netmask}")
+ else
+ IPAddr.new(new_resource.target)
+ end
+
+ # For linux, we use /proc/net/route file to read proc table info
+ return unless linux?
+
+ route_file = ::File.open("/proc/net/route", "r")
+
+ # Read all routes
+ while (line = route_file.gets)
+ # Get all the fields for a route
+ _, destination, gateway, _, _, _, _, mask = line.split
+
+ # Convert hex-encoded values to quad-dotted notation (e.g. 0064A8C0 => 192.168.100.0)
+ destination = hex2ip(destination)
+ gateway = hex2ip(gateway)
+ mask = hex2ip(mask)
+
+ # Skip formatting lines (header, etc)
+ next unless destination && gateway && mask
+
+ logger.trace("#{new_resource} system has route: dest=#{destination} mask=#{mask} gw=#{gateway}")
+
+ # check if what were trying to configure is already there
+ # use an ipaddr object with ip/mask this way we can have
+ # a new resource be in cidr format (i don't feel like
+ # expanding bitmask by hand.
+ #
+ running_ip = IPAddr.new("#{destination}/#{mask}")
+ logger.trace("#{new_resource} new ip: #{new_ip.inspect} running ip: #{running_ip.inspect}")
+ self.is_running = true if running_ip == new_ip && gateway == new_resource.gateway
+ end
- # For linux, we use /proc/net/route file to read proc table info
- if node[:os] == "linux"
- route_file = ::File.open("/proc/net/route", "r")
-
- # Read all routes
- while (line = route_file.gets)
- # Get all the fields for a route
- iface, destination, gateway, flags, refcnt, use, metric, mask, mtu, window, irtt = line.split
-
- # Convert hex-encoded values to quad-dotted notation (e.g. 0064A8C0 => 192.168.100.0)
- destination = hex2ip(destination)
- gateway = hex2ip(gateway)
- mask = hex2ip(mask)
-
- # Skip formatting lines (header, etc)
- next unless destination && gateway && mask
- Chef::Log.debug("#{@new_resource} system has route: dest=#{destination} mask=#{mask} gw=#{gateway}")
-
- # check if what were trying to configure is already there
- # use an ipaddr object with ip/mask this way we can have
- # a new resource be in cidr format (i don't feel like
- # expanding bitmask by hand.
- #
- running_ip = IPAddr.new("#{destination}/#{mask}")
- Chef::Log.debug("#{@new_resource} new ip: #{new_ip.inspect} running ip: #{running_ip.inspect}")
- self.is_running = true if running_ip == new_ip && gateway == @new_resource.gateway
+ route_file.close
end
- route_file.close
- end
- end
+ action :add do
+ # check to see if load_current_resource found the route
+ if is_running
+ logger.trace("#{new_resource} route already active - nothing to do")
+ else
+ command = generate_command(:add)
+ converge_by("run #{command.join(" ")} to add route") do
+ shell_out!(*command)
+ logger.info("#{new_resource} added")
+ end
+ end
- def action_add
- # check to see if load_current_resource found the route
- if is_running
- Chef::Log.debug("#{@new_resource} route already active - nothing to do")
- else
- command = generate_command(:add)
- converge_by ("run #{ command } to add route") do
- run_command( :command => command )
- Chef::Log.info("#{@new_resource} added")
+ # for now we always write the file (ugly but its what it is)
+ generate_config
end
- end
- #for now we always write the file (ugly but its what it is)
- generate_config
- end
+ action :delete do
+ if is_running
+ command = generate_command(:delete)
+ converge_by("run #{command.join(" ")} to delete route ") do
+ shell_out!(*command)
+ logger.info("#{new_resource} removed")
+ end
+ else
+ logger.trace("#{new_resource} route does not exist - nothing to do")
+ end
- def action_delete
- if is_running
- command = generate_command(:delete)
- converge_by ("run #{ command } to delete route ") do
- run_command( :command => command )
- Chef::Log.info("#{@new_resource} removed")
+ # for now we always write the file (ugly but its what it is)
+ generate_config
end
- else
- Chef::Log.debug("#{@new_resource} route does not exist - nothing to do")
- end
-
- #for now we always write the file (ugly but its what it is)
- generate_config
- end
- def generate_config
- conf = Hash.new
- case node[:platform]
- when "centos", "redhat", "fedora"
- # walk the collection
- run_context.resource_collection.each do |resource|
- if resource.is_a? Chef::Resource::Route
- # default to eth0
- if resource.device
- dev = resource.device
- else
- dev = "eth0"
+ def generate_config
+ if platform_family?("rhel", "amazon", "fedora")
+ conf = {}
+ # FIXME FIXME FIXME FIXME: whatever this walking-the-run-context API is, it needs to be removed.
+ # walk the collection
+ rc = run_context.parent_run_context || run_context
+ rc.resource_collection.each do |resource|
+ next unless resource.is_a? Chef::Resource::Route
+
+ # default to eth0
+ dev = resource.device || "eth0"
+
+ conf[dev] = "" if conf[dev].nil?
+ case @action
+ when :add
+ conf[dev] << config_file_contents(:add, comment: resource.comment, device: resource.device, target: resource.target, metric: resource.metric, netmask: resource.netmask, gateway: resource.gateway) if resource.action == [:add]
+ when :delete
+ # need to do this for the case when the last route on an int
+ # is removed
+ conf[dev] << config_file_contents(:delete)
+ end
end
-
- conf[dev] = String.new if conf[dev].nil?
- case @action
- when :add
- conf[dev] << config_file_contents(:add, :target => resource.target, :netmask => resource.netmask, :gateway => resource.gateway) if resource.action == [:add]
- when :delete
- # need to do this for the case when the last route on an int
- # is removed
- conf[dev] << config_file_contents(:delete)
+ conf.each_key do |k|
+ if new_resource.target == "default"
+ network_file_name = "/etc/sysconfig/network"
+ converge_by("write route default route to #{network_file_name}") do
+ logger.trace("#{new_resource} writing default route #{new_resource.gateway} to #{network_file_name}")
+ if ::File.exist?(network_file_name)
+ network_file = ::Chef::Util::FileEdit.new(network_file_name)
+ network_file.search_file_replace_line(/^GATEWAY=/, "GATEWAY=#{new_resource.gateway}")
+ network_file.insert_line_if_no_match(/^GATEWAY=/, "GATEWAY=#{new_resource.gateway}")
+ network_file.write_file
+ else
+ network_file = ::File.new(network_file_name, "w")
+ network_file.puts("GATEWAY=#{new_resource.gateway}")
+ network_file.close
+ end
+ end
+ else
+ network_file_name = "/etc/sysconfig/network-scripts/route-#{k}"
+ converge_by("write route route.#{k}\n#{conf[k]} to #{network_file_name}") do
+ network_file = ::File.new(network_file_name, "w")
+ network_file.puts(conf[k])
+ logger.trace("#{new_resource} writing route.#{k}\n#{conf[k]}")
+ network_file.close
+ end
+ end
end
end
end
- conf.each do |k, v|
- network_file_name = "/etc/sysconfig/network-scripts/route-#{k}"
- converge_by ("write route route.#{k}\n#{conf[k]} to #{ network_file_name }") do
- network_file = ::File.new(network_file_name, "w")
- network_file.puts(conf[k])
- Chef::Log.debug("#{@new_resource} writing route.#{k}\n#{conf[k]}")
- network_file.close
+
+ def generate_command(action)
+ target = new_resource.target
+ target = "#{target}/#{MASK[new_resource.netmask.to_s]}" if new_resource.netmask
+
+ case action
+ when :add
+ command = [ "ip", "route", "replace", target ]
+ command += [ "via", new_resource.gateway ] if new_resource.gateway
+ command += [ "dev", new_resource.device ] if new_resource.device
+ command += [ "metric", new_resource.metric ] if new_resource.metric
+ when :delete
+ command = [ "ip", "route", "delete", target ]
+ command += [ "via", new_resource.gateway ] if new_resource.gateway
end
- end
- end
- end
- def generate_command(action)
- 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
-
- case action
- when :add
- command = "ip route replace #{@new_resource.target}"
- command << common_route_items
- command << " dev #{@new_resource.device} " if @new_resource.device
- when :delete
- command = "ip route delete #{@new_resource.target}"
- command << common_route_items
- end
+ command
+ end
- return command
- end
+ def config_file_contents(action, options = {})
+ content = ""
+ case action
+ when :add
+ content << "# #{options[:comment]}\n" if options[:comment]
+ content << (options[:target]).to_s
+ content << "/#{MASK[options[:netmask].to_s]}" if options[:netmask]
+ content << " via #{options[:gateway]}" if options[:gateway]
+ content << " dev #{options[:device]}" if options[:device]
+ content << " metric #{options[:metric]}" if options[:metric]
+ content << "\n"
+ end
- def config_file_contents(action, options = {})
- content = ""
- case action
- when :add
- content << "#{options[:target]}"
- content << "/#{options[:netmask]}" if options[:netmask]
- content << " via #{options[:gateway]}" if options[:gateway]
- content << "\n"
+ content
+ end
end
-
- return content
end
end
diff --git a/lib/chef/provider/ruby_block.rb b/lib/chef/provider/ruby_block.rb
index 0817b14044..80f96205dc 100644
--- a/lib/chef/provider/ruby_block.rb
+++ b/lib/chef/provider/ruby_block.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: AJ Christensen (<aj@chef.io>)
-# Copyright:: Copyright 2009-2016, Opscode
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,20 +20,16 @@
class Chef
class Provider
class RubyBlock < Chef::Provider
- provides :ruby_block
-
- def whyrun_supported?
- true
- end
+ provides :ruby_block, target_mode: true
def load_current_resource
true
end
- def action_run
- converge_by("execute the ruby block #{@new_resource.name}") do
- @new_resource.block.call
- Chef::Log.info("#{@new_resource} called")
+ action :run do
+ converge_by("execute the ruby block #{new_resource.name}") do
+ new_resource.block.call
+ logger.info("#{new_resource} called")
end
end
diff --git a/lib/chef/provider/script.rb b/lib/chef/provider/script.rb
index 6ca4e9f6f3..8e4951bbcb 100644
--- a/lib/chef/provider/script.rb
+++ b/lib/chef/provider/script.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,8 @@
# limitations under the License.
#
-require "tempfile"
-require "chef/provider/execute"
-require "forwardable"
+require_relative "execute"
+require "forwardable" unless defined?(Forwardable)
class Chef
class Provider
@@ -33,53 +32,15 @@ class Chef
provides :ruby
provides :script
- def_delegators :@new_resource, :interpreter, :flags
-
- attr_accessor :code
-
- def initialize(new_resource, run_context)
- super
- self.code = new_resource.code
- end
+ def_delegators :new_resource, :interpreter, :flags, :code
def command
- "\"#{interpreter}\" #{flags} \"#{script_file.path}\""
- end
-
- def load_current_resource
- super
- # @todo Chef-13: change this to an exception
- if code.nil?
- Chef::Log.warn "#{@new_resource}: No code attribute was given, resource does nothing, this behavior is deprecated and will be removed in Chef-13"
- end
+ "\"#{interpreter}\" #{flags}"
end
- def action_run
- script_file.puts(code)
- script_file.close
-
- set_owner_and_group
-
- super
-
- unlink_script_file
+ def input
+ code
end
-
- def set_owner_and_group
- # FileUtils itself implements a no-op if +user+ or +group+ are nil
- # You can prove this by running FileUtils.chown(nil,nil,'/tmp/file')
- # as an unprivileged user.
- FileUtils.chown(new_resource.user, new_resource.group, script_file.path)
- end
-
- def script_file
- @script_file ||= Tempfile.open("chef-script")
- end
-
- def unlink_script_file
- script_file && script_file.close!
- end
-
end
end
end
diff --git a/lib/chef/provider/service.rb b/lib/chef/provider/service.rb
index e693bd2eed..7a2d2fc86c 100644
--- a/lib/chef/provider/service.rb
+++ b/lib/chef/provider/service.rb
@@ -1,7 +1,7 @@
#
# Author:: AJ Christensen (<aj@hjksolutions.com>)
# Author:: Davide Cavalca (<dcavalca@fb.com>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,14 +17,14 @@
# limitations under the License.
#
-require "chef/mixin/command"
-require "chef/provider"
+require_relative "../provider"
+require "chef-utils" unless defined?(ChefUtils::CANARY)
class Chef
class Provider
class Service < Chef::Provider
-
- include Chef::Mixin::Command
+ include Chef::Platform::ServiceHelpers
+ extend Chef::Platform::ServiceHelpers
def supports
@supports ||= new_resource.supports.dup
@@ -35,10 +35,6 @@ class Chef
@enabled = nil
end
- def whyrun_supported?
- true
- end
-
def load_current_resource
supports[:status] = false if supports[:status].nil?
supports[:reload] = false if supports[:reload].nil?
@@ -51,21 +47,21 @@ class Chef
# 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)
+ if new_resource.enabled.nil?
+ new_resource.enabled(current_resource.enabled)
end
- if @new_resource.running.nil?
- @new_resource.running(@current_resource.running)
+ if new_resource.running.nil?
+ new_resource.running(current_resource.running)
end
- if @new_resource.masked.nil?
- @new_resource.masked(@current_resource.masked)
+ if new_resource.masked.nil?
+ new_resource.masked(current_resource.masked)
end
end
# subclasses should override this if they do implement user services
def user_services_requirements
requirements.assert(:all_actions) do |a|
- a.assertion { @new_resource.user.nil? }
+ a.assertion { new_resource.user.nil? }
a.failure_message Chef::Exceptions::UnsupportedAction, "#{self} does not support user services"
end
end
@@ -76,7 +72,7 @@ class Chef
def define_resource_requirements
requirements.assert(:reload) do |a|
- a.assertion { supports[:reload] || @new_resource.reload_command }
+ 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
@@ -84,98 +80,98 @@ class Chef
end
end
- def action_enable
- if @current_resource.enabled
- Chef::Log.debug("#{@new_resource} already enabled - nothing to do")
+ action :enable do
+ if current_resource.enabled
+ logger.trace("#{new_resource} already enabled - nothing to do")
else
- converge_by("enable service #{@new_resource}") do
+ converge_by("enable service #{new_resource}") do
enable_service
- Chef::Log.info("#{@new_resource} enabled")
+ logger.info("#{new_resource} enabled")
end
end
load_new_resource_state
- @new_resource.enabled(true)
+ new_resource.enabled(true)
end
- def action_disable
- if @current_resource.enabled
- converge_by("disable service #{@new_resource}") do
+ action :disable do
+ if current_resource.enabled
+ converge_by("disable service #{new_resource}") do
disable_service
- Chef::Log.info("#{@new_resource} disabled")
+ logger.info("#{new_resource} disabled")
end
else
- Chef::Log.debug("#{@new_resource} already disabled - nothing to do")
+ logger.trace("#{new_resource} already disabled - nothing to do")
end
load_new_resource_state
- @new_resource.enabled(false)
+ new_resource.enabled(false)
end
- def action_mask
- if @current_resource.masked
- Chef::Log.debug("#{@new_resource} already masked - nothing to do")
+ action :mask do
+ if current_resource.masked
+ logger.trace("#{new_resource} already masked - nothing to do")
else
- converge_by("mask service #{@new_resource}") do
+ converge_by("mask service #{new_resource}") do
mask_service
- Chef::Log.info("#{@new_resource} masked")
+ logger.info("#{new_resource} masked")
end
end
load_new_resource_state
- @new_resource.masked(true)
+ new_resource.masked(true)
end
- def action_unmask
- if @current_resource.masked
- converge_by("unmask service #{@new_resource}") do
+ action :unmask do
+ if current_resource.masked
+ converge_by("unmask service #{new_resource}") do
unmask_service
- Chef::Log.info("#{@new_resource} unmasked")
+ logger.info("#{new_resource} unmasked")
end
else
- Chef::Log.debug("#{@new_resource} already unmasked - nothing to do")
+ logger.trace("#{new_resource} already unmasked - nothing to do")
end
load_new_resource_state
- @new_resource.masked(false)
+ new_resource.masked(false)
end
- def action_start
- unless @current_resource.running
- converge_by("start service #{@new_resource}") do
+ action :start do
+ unless current_resource.running
+ converge_by("start service #{new_resource}") do
start_service
- Chef::Log.info("#{@new_resource} started")
+ logger.info("#{new_resource} started")
end
else
- Chef::Log.debug("#{@new_resource} already running - nothing to do")
+ logger.trace("#{new_resource} already running - nothing to do")
end
load_new_resource_state
- @new_resource.running(true)
+ new_resource.running(true)
end
- def action_stop
- if @current_resource.running
- converge_by("stop service #{@new_resource}") do
+ action :stop do
+ if current_resource.running
+ converge_by("stop service #{new_resource}") do
stop_service
- Chef::Log.info("#{@new_resource} stopped")
+ logger.info("#{new_resource} stopped")
end
else
- Chef::Log.debug("#{@new_resource} already stopped - nothing to do")
+ logger.trace("#{new_resource} already stopped - nothing to do")
end
load_new_resource_state
- @new_resource.running(false)
+ new_resource.running(false)
end
- def action_restart
- converge_by("restart service #{@new_resource}") do
+ action :restart do
+ converge_by("restart service #{new_resource}") do
restart_service
- Chef::Log.info("#{@new_resource} restarted")
+ logger.info("#{new_resource} restarted")
end
load_new_resource_state
- @new_resource.running(true)
+ new_resource.running(true)
end
- def action_reload
- if @current_resource.running
- converge_by("reload service #{@new_resource}") do
+ action :reload do
+ if current_resource.running
+ converge_by("reload service #{new_resource}") do
reload_service
- Chef::Log.info("#{@new_resource} reloaded")
+ logger.info("#{new_resource} reloaded")
end
end
load_new_resource_state
@@ -216,17 +212,17 @@ class Chef
protected
def default_init_command
- if @new_resource.init_command
- @new_resource.init_command
- elsif self.instance_variable_defined?(:@init_command)
+ if new_resource.init_command
+ new_resource.init_command
+ elsif instance_variable_defined?(:@init_command)
@init_command
end
end
def custom_command_for_action?(action)
method_name = "#{action}_command".to_sym
- @new_resource.respond_to?(method_name) &&
- !!@new_resource.send(method_name)
+ new_resource.respond_to?(method_name) &&
+ !!new_resource.send(method_name)
end
module ServicePriorityInit
@@ -239,20 +235,20 @@ class Chef
# 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"
+ require_relative "../chef_class"
+ require_relative "service/systemd"
+ require_relative "service/insserv"
+ require_relative "service/redhat"
+ require_relative "service/arch"
+ require_relative "service/gentoo"
+ require_relative "service/upstart"
+ require_relative "service/debian"
+ require_relative "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}
+ Chef.set_provider_priority_array :service, [ Systemd, Insserv, Redhat ], platform_family: "rpm_based"
end
end
end
diff --git a/lib/chef/provider/service/aix.rb b/lib/chef/provider/service/aix.rb
index 201f9ff5f9..54355a7bc1 100644
--- a/lib/chef/provider/service/aix.rb
+++ b/lib/chef/provider/service/aix.rb
@@ -1,6 +1,6 @@
#
# Author:: kaustubh (<kaustubh@clogeny.com>)
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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/provider/service"
+require_relative "../service"
class Chef
class Provider
@@ -43,10 +43,6 @@ class Chef
@current_resource
end
- def whyrun_supported?
- true
- end
-
def start_service
if @is_resource_group
shell_out!("startsrc -g #{@new_resource.service_name}")
@@ -92,7 +88,7 @@ class Chef
protected
def determine_current_status!
- Chef::Log.debug "#{@new_resource} using lssrc to check the status"
+ logger.trace "#{@new_resource} using lssrc to check the status"
begin
if is_resource_group?
# Groups as a whole have no notion of whether they're running
@@ -105,7 +101,7 @@ class Chef
@current_resource.running false
end
end
- Chef::Log.debug "#{@new_resource} running: #{@current_resource.running}"
+ logger.trace "#{@new_resource} running: #{@current_resource.running}"
# ShellOut sometimes throws different types of Exceptions than ShellCommandFailed.
# Temporarily catching different types of exceptions here until we get Shellout fixed.
# TODO: Remove the line before one we get the ShellOut fix.
@@ -119,7 +115,7 @@ class Chef
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")
+ logger.trace("#{@new_resource.service_name} is a group")
@is_resource_group = true
end
end
diff --git a/lib/chef/provider/service/aixinit.rb b/lib/chef/provider/service/aixinit.rb
index 73c5e07715..e845629fe7 100644
--- a/lib/chef/provider/service/aixinit.rb
+++ b/lib/chef/provider/service/aixinit.rb
@@ -1,6 +1,6 @@
#
# Author:: kaustubh (<kaustubh@clogeny.com>)
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,13 +16,13 @@
# limitations under the License.
#
-require "chef/provider/service/init"
+require_relative "init"
class Chef
class Provider
class Service
class AixInit < Chef::Provider::Service::Init
- RC_D_SCRIPT_NAME = /\/etc\/rc.d\/rc2.d\/([SK])(\d\d|)/i
+ RC_D_SCRIPT_NAME = %r{/etc/rc.d/rc2.d/([SK])(\d\d|)}i.freeze
def initialize(new_resource, run_context)
super
@@ -38,18 +38,18 @@ class Chef
@current_resource
end
- def action_enable
+ action :enable do
if @new_resource.priority.nil?
priority_ok = true
else
priority_ok = @current_resource.priority == @new_resource.priority
end
if @current_resource.enabled && priority_ok
- Chef::Log.debug("#{@new_resource} already enabled - nothing to do")
+ logger.trace("#{@new_resource} already enabled - nothing to do")
else
converge_by("enable service #{@new_resource}") do
enable_service
- Chef::Log.info("#{@new_resource} enabled")
+ logger.info("#{@new_resource} enabled")
end
end
load_new_resource_state
diff --git a/lib/chef/provider/service/arch.rb b/lib/chef/provider/service/arch.rb
index 2fd32e37aa..400da506b1 100644
--- a/lib/chef/provider/service/arch.rb
+++ b/lib/chef/provider/service/arch.rb
@@ -16,14 +16,14 @@
# limitations under the License.
#
-require "chef/provider/service/init"
+require_relative "init"
class Chef::Provider::Service::Arch < Chef::Provider::Service::Init
provides :service, platform_family: "arch"
def self.supports?(resource, action)
- Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:etc_rcd)
+ service_script_exist?(:etc_rcd, resource.service_name)
end
def initialize(new_resource, run_context)
@@ -32,8 +32,9 @@ class Chef::Provider::Service::Arch < Chef::Provider::Service::Init
end
def load_current_resource
- raise Chef::Exceptions::Service, "Could not find /etc/rc.conf" unless ::File.exists?("/etc/rc.conf")
- raise Chef::Exceptions::Service, "No DAEMONS found in /etc/rc.conf" unless ::File.read("/etc/rc.conf") =~ /DAEMONS=\((.*)\)/m
+ raise Chef::Exceptions::Service, "Could not find /etc/rc.conf" unless ::File.exist?("/etc/rc.conf")
+ raise Chef::Exceptions::Service, "No DAEMONS found in /etc/rc.conf" unless /DAEMONS=\((.*)\)/m.match?(::File.read("/etc/rc.conf"))
+
super
@current_resource.enabled(daemons.include?(@current_resource.service_name))
@@ -41,7 +42,7 @@ class Chef::Provider::Service::Arch < Chef::Provider::Service::Init
end
# Get list of all daemons from the file '/etc/rc.conf'.
- # Mutiple lines and background form are supported. Example:
+ # Multiple lines and background form are supported. Example:
# DAEMONS=(\
# foobar \
# @example \
@@ -60,13 +61,13 @@ class Chef::Provider::Service::Arch < Chef::Provider::Service::Init
# FIXME: Multiple entries of DAEMONS will cause very bad results :)
def update_daemons(entries)
- content = ::File.read("/etc/rc.conf").gsub(/DAEMONS=\((.*)\)/m, "DAEMONS=(#{entries.join(' ')})")
+ content = ::File.read("/etc/rc.conf").gsub(/DAEMONS=\((.*)\)/m, "DAEMONS=(#{entries.join(" ")})")
::File.open("/etc/rc.conf", "w") do |f|
f.write(content)
end
end
- def enable_service()
+ def enable_service
new_daemons = []
entries = daemons
@@ -92,7 +93,7 @@ class Chef::Provider::Service::Arch < Chef::Provider::Service::Init
end
end
- def disable_service()
+ def disable_service
new_daemons = []
entries = daemons
diff --git a/lib/chef/provider/service/debian.rb b/lib/chef/provider/service/debian.rb
index 9d11032055..17e0b19b9c 100644
--- a/lib/chef/provider/service/debian.rb
+++ b/lib/chef/provider/service/debian.rb
@@ -1,6 +1,6 @@
#
# Author:: AJ Christensen (<aj@hjksolutions.com>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,27 +16,26 @@
# limitations under the License.
#
-require "chef/provider/service/init"
+require_relative "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)
+ provides :service, platform_family: "debian" do
+ debianrcd?
end
- UPDATE_RC_D_ENABLED_MATCHES = /\/rc[\dS].d\/S|not installed/i
- UPDATE_RC_D_PRIORITIES = /\/rc([\dS]).d\/([SK])(\d\d)/i
+ UPDATE_RC_D_ENABLED_MATCHES = %r{/rc[\dS].d/S|not installed}i.freeze
+ UPDATE_RC_D_PRIORITIES = %r{/rc([\dS]).d/([SK])(\d\d)}i.freeze
+ RUNLEVELS = %w{ 1 2 3 4 5 S }.freeze
def self.supports?(resource, action)
- Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:initd)
+ service_script_exist?(:initd, resource.service_name)
end
def load_current_resource
super
- @priority_success = true
- @rcd_status = nil
current_resource.priority(get_priority)
current_resource.enabled(service_currently_enabled?(current_resource.priority))
current_resource
@@ -47,48 +46,64 @@ class Chef
shared_resource_requirements
requirements.assert(:all_actions) do |a|
update_rcd = "/usr/sbin/update-rc.d"
- a.assertion { ::File.exists? update_rcd }
+ a.assertion { ::File.exist? update_rcd }
a.failure_message Chef::Exceptions::Service, "#{update_rcd} does not exist!"
# no whyrun recovery - this is a base system component of debian
# distros and must be present
end
requirements.assert(:all_actions) do |a|
- a.assertion { @priority_success }
- a.failure_message Chef::Exceptions::Service, "/usr/sbin/update-rc.d -n -f #{current_resource.service_name} failed - #{@rcd_status.inspect}"
+ a.assertion { @got_priority == true }
+ a.failure_message Chef::Exceptions::Service, "Unable to determine priority for service"
# This can happen if the service is not yet installed,so we'll fake it.
a.whyrun ["Unable to determine priority of service, assuming service would have been correctly installed earlier in the run.",
"Assigning temporary priorities to continue.",
"If this service is not properly installed prior to this point, this will fail."] do
- temp_priorities = { "6" => [:stop, "20"],
- "0" => [:stop, "20"],
- "1" => [:stop, "20"],
- "2" => [:start, "20"],
- "3" => [:start, "20"],
- "4" => [:start, "20"],
- "5" => [:start, "20"] }
- current_resource.priority(temp_priorities)
- end
+ temp_priorities = { "6" => [:stop, "20"],
+ "0" => [:stop, "20"],
+ "1" => [:stop, "20"],
+ "2" => [:start, "20"],
+ "3" => [:start, "20"],
+ "4" => [:start, "20"],
+ "5" => [:start, "20"] }
+ current_resource.priority(temp_priorities)
+ end
end
end
+ # returns a list of levels that the service should be stopped or started on
+ def parse_init_file(path)
+ return [] unless ::File.exist?(path)
+
+ in_info = false
+ ::File.readlines(path).each_with_object([]) do |line, acc|
+ if /^### BEGIN INIT INFO/.match?(line)
+ in_info = true
+ elsif /^### END INIT INFO/.match?(line)
+ break acc
+ elsif in_info
+ if line =~ /Default-(Start|Stop):\s+(\d.*)/
+ acc << $2.split(" ")
+ end
+ end
+ end.flatten
+ end
+
def get_priority
priority = {}
+ rc_files = []
- @rcd_status = popen4("/usr/sbin/update-rc.d -n -f #{current_resource.service_name} remove") do |pid, stdin, stdout, stderr|
-
- [stdout, stderr].each do |iop|
- iop.each_line do |line|
- if UPDATE_RC_D_PRIORITIES =~ line
- # priority[runlevel] = [ S|K, priority ]
- # S = Start, K = Kill
- # debian runlevels: 0 Halt, 1 Singleuser, 2 Multiuser, 3-5 == 2, 6 Reboot
- priority[$1] = [($2 == "S" ? :start : :stop), $3]
- end
- if line =~ UPDATE_RC_D_ENABLED_MATCHES
- enabled = true
- end
- end
+ levels = parse_init_file(@init_command)
+ levels.each do |level|
+ rc_files.push Dir.glob("/etc/rc#{level}.d/[SK][0-9][0-9]#{current_resource.service_name}")
+ end
+
+ rc_files.flatten.each do |line|
+ if UPDATE_RC_D_PRIORITIES =~ line
+ # priority[runlevel] = [ S|K, priority ]
+ # S = Start, K = Kill
+ # debian runlevels: 0 Halt, 1 Singleuser, 2 Multiuser, 3-5 == 2, 6 Reboot
+ priority[$1] = [($2 == "S" ? :start : :stop), $3]
end
end
@@ -98,18 +113,16 @@ class Chef
priority = priority[2].last
end
- unless @rcd_status.exitstatus == 0
- @priority_success = false
- end
+ @got_priority = true
priority
end
def service_currently_enabled?(priority)
enabled = false
priority.each do |runlevel, arguments|
- Chef::Log.debug("#{new_resource} runlevel #{runlevel}, action #{arguments[0]}, priority #{arguments[1]}")
+ logger.trace("#{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 RUNLEVELS.include?(runlevel) && arguments[0] == :start
enabled = true
end
end
@@ -118,18 +131,18 @@ class Chef
end
# Override method from parent to ensure priority is up-to-date
- def action_enable
+ action :enable do
if new_resource.priority.nil?
priority_ok = true
else
priority_ok = @current_resource.priority == new_resource.priority
end
if current_resource.enabled && priority_ok
- Chef::Log.debug("#{new_resource} already enabled - nothing to do")
+ logger.trace("#{new_resource} already enabled - nothing to do")
else
converge_by("enable service #{new_resource}") do
enable_service
- Chef::Log.info("#{new_resource} enabled")
+ logger.info("#{new_resource} enabled")
end
end
load_new_resource_state
@@ -137,44 +150,46 @@ class Chef
end
def enable_service
- if new_resource.priority.is_a? Integer
- shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove")
- shell_out!("/usr/sbin/update-rc.d #{new_resource.service_name} defaults #{new_resource.priority} #{100 - new_resource.priority}")
- elsif new_resource.priority.is_a? Hash
- # we call the same command regardless of we're enabling or disabling
- # users passing a Hash are responsible for setting their own start priorities
+ # We call the same command regardless if we're enabling or disabling
+ # Users passing a Hash are responsible for setting their own stop priorities
+ if new_resource.priority.is_a? Hash
set_priority
- else # No priority, go with update-rc.d defaults
- shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove")
- shell_out!("/usr/sbin/update-rc.d #{new_resource.service_name} defaults")
+ return
end
+
+ start_priority = new_resource.priority.is_a?(Integer) ? new_resource.priority : 20
+ # Stop processes in reverse order of start using '100 - start_priority'.
+ stop_priority = 100 - start_priority
+
+ shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove")
+ shell_out!("/usr/sbin/update-rc.d #{new_resource.service_name} defaults #{start_priority} #{stop_priority}")
end
def disable_service
- if new_resource.priority.is_a? Integer
- # Stop processes in reverse order of start using '100 - start_priority'
- shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove")
- shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} stop #{100 - new_resource.priority} 2 3 4 5 .")
- elsif new_resource.priority.is_a? Hash
- # we call the same command regardless of we're enabling or disabling
- # users passing a Hash are responsible for setting their own stop priorities
+ if new_resource.priority.is_a? Hash
+ # We call the same command regardless if we're enabling or disabling
+ # Users passing a Hash are responsible for setting their own stop priorities
set_priority
- else
- # no priority, using '100 - 20 (update-rc.d default)' to stop in reverse order of start
- shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove")
- shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} stop 80 2 3 4 5 .")
+ return
end
+
+ shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove")
+ shell_out!("/usr/sbin/update-rc.d #{new_resource.service_name} defaults")
+ shell_out!("/usr/sbin/update-rc.d #{new_resource.service_name} disable")
end
def set_priority
- args = ""
- new_resource.priority.each do |level, o|
- action = o[0]
- priority = o[1]
- args += "#{action} #{priority} #{level} . "
- end
shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove")
- shell_out!("/usr/sbin/update-rc.d #{new_resource.service_name} #{args}")
+
+ # Reset priorities to default values before applying customizations. This way
+ # the final state will always be consistent, regardless if all runlevels were
+ # provided.
+ shell_out!("/usr/sbin/update-rc.d #{new_resource.service_name} defaults")
+ new_resource.priority.each do |level, (action, _priority)|
+ disable_or_enable = (action == :start ? "enable" : "disable")
+
+ shell_out!("/usr/sbin/update-rc.d #{new_resource.service_name} #{disable_or_enable} #{level}")
+ end
end
end
end
diff --git a/lib/chef/provider/service/freebsd.rb b/lib/chef/provider/service/freebsd.rb
index 76d8c1d17b..5f6ee0ff74 100644
--- a/lib/chef/provider/service/freebsd.rb
+++ b/lib/chef/provider/service/freebsd.rb
@@ -16,9 +16,8 @@
# limitations under the License.
#
-require "chef/resource/service"
-require "chef/provider/service/init"
-require "chef/mixin/command"
+require_relative "../../resource/service"
+require_relative "init"
class Chef
class Provider
@@ -48,7 +47,7 @@ class Chef
return current_resource unless init_command
- Chef::Log.debug("#{current_resource} found at #{init_command}")
+ logger.trace("#{current_resource} found at #{init_command}")
@status_load_success = true
determine_current_status! # see Chef::Provider::Service::Simple
@@ -68,13 +67,13 @@ class Chef
requirements.assert(:all_actions) do |a|
a.assertion { enabled_state_found }
- # for consistentcy with original behavior, this will not fail in non-whyrun mode;
+ # for consistency with original behavior, this will not fail in non-whyrun mode;
# rather it will silently set enabled state=>false
a.whyrun "Unable to determine enabled/disabled state, assuming this will be correct for an actual run. Assuming disabled."
end
requirements.assert(:start, :enable, :reload, :restart) do |a|
- a.assertion { service_enable_variable_name != nil }
+ a.assertion { !service_enable_variable_name.nil? }
a.failure_message Chef::Exceptions::Service, "Could not find the service name in #{init_command} and rcvar"
# No recovery in whyrun mode - the init file is present but not correct.
end
@@ -84,7 +83,7 @@ class Chef
if new_resource.start_command
super
else
- shell_out_with_systems_locale!("#{init_command} faststart")
+ shell_out!("#{init_command} faststart", default_env: false)
end
end
@@ -92,7 +91,7 @@ class Chef
if new_resource.stop_command
super
else
- shell_out_with_systems_locale!("#{init_command} faststop")
+ shell_out!("#{init_command} faststop", default_env: false)
end
end
@@ -100,7 +99,7 @@ class Chef
if new_resource.restart_command
super
elsif supports[:restart]
- shell_out_with_systems_locale!("#{init_command} fastrestart")
+ shell_out!("#{init_command} fastrestart", default_env: false)
else
stop_service
sleep 1
@@ -119,7 +118,7 @@ class Chef
private
def read_rc_conf
- ::File.open("/etc/rc.conf", "r") { |file| file.readlines }
+ ::File.open("/etc/rc.conf", "r", &:readlines)
end
def write_rc_conf(lines)
@@ -146,7 +145,7 @@ class Chef
end
# some scripts support multiple instances through symlinks such as openvpn.
# We should get the service name from rcvar.
- Chef::Log.debug("name=\"service\" not found at #{init_command}. falling back to rcvar")
+ logger.trace("name=\"service\" not found at #{init_command}. falling back to rcvar")
shell_out!("#{init_command} rcvar").stdout[/(\w+_enable)=/, 1]
else
# for why-run mode when the rcd_script is not there yet
@@ -162,9 +161,9 @@ class Chef
case line
when /^#{Regexp.escape(var_name)}="(\w+)"/
enabled_state_found!
- if $1 =~ /^yes$/i
+ if $1.casecmp?("yes")
current_resource.enabled true
- elsif $1 =~ /^(no|none)$/i
+ elsif $1.casecmp?("no") || $1.casecmp?("none")
current_resource.enabled false
end
end
@@ -172,7 +171,7 @@ class Chef
end
if current_resource.enabled.nil?
- Chef::Log.debug("#{new_resource.name} enable/disable state unknown")
+ logger.trace("#{new_resource.name} enable/disable state unknown")
current_resource.enabled false
end
end
diff --git a/lib/chef/provider/service/gentoo.rb b/lib/chef/provider/service/gentoo.rb
index 8fb6d1f9af..4678435129 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@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,9 +17,8 @@
# limitations under the License.
#
-require "chef/provider/service/init"
-require "chef/mixin/command"
-require "chef/util/path_helper"
+require_relative "init"
+require_relative "../../util/path_helper"
class Chef::Provider::Service::Gentoo < Chef::Provider::Service::Init
@@ -35,20 +34,20 @@ class Chef::Provider::Service::Gentoo < Chef::Provider::Service::Init
@current_resource.enabled(
Dir.glob("/etc/runlevels/**/#{Chef::Util::PathHelper.escape_glob_dir(@current_resource.service_name)}").any? do |file|
@found_script = true
- exists = ::File.exists? file
+ exists = ::File.exist? file
readable = ::File.readable? file
- Chef::Log.debug "#{@new_resource} exists: #{exists}, readable: #{readable}"
+ logger.trace "#{@new_resource} exists: #{exists}, readable: #{readable}"
exists && readable
end
)
- Chef::Log.debug "#{@new_resource} enabled: #{@current_resource.enabled}"
+ logger.trace "#{@new_resource} enabled: #{@current_resource.enabled}"
@current_resource
end
def define_resource_requirements
requirements.assert(:all_actions) do |a|
- a.assertion { ::File.exists?("/sbin/rc-update") }
+ a.assertion { ::File.exist?("/sbin/rc-update") }
a.failure_message Chef::Exceptions::Service, "/sbin/rc-update does not exist"
# no whyrun recovery -t his is a core component whose presence is
# unlikely to be affected by what we do in the course of a chef run
@@ -61,11 +60,11 @@ class Chef::Provider::Service::Gentoo < Chef::Provider::Service::Init
end
end
- def enable_service()
+ def enable_service
shell_out!("/sbin/rc-update add #{@new_resource.service_name} default")
end
- def disable_service()
+ def disable_service
shell_out!("/sbin/rc-update del #{@new_resource.service_name} default")
end
end
diff --git a/lib/chef/provider/service/init.rb b/lib/chef/provider/service/init.rb
index dff627d016..a4225fa89d 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 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,8 @@
# limitations under the License.
#
-require "chef/provider/service/simple"
-require "chef/mixin/command"
-require "chef/platform/service_helpers"
+require_relative "simple"
+require_relative "../../platform/service_helpers"
class Chef
class Provider
@@ -30,7 +29,7 @@ class Chef
provides :service, os: "!windows"
def self.supports?(resource, action)
- Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:initd)
+ service_script_exist?(:initd, resource.service_name)
end
def initialize(new_resource, run_context)
@@ -57,7 +56,7 @@ class Chef
if @new_resource.start_command
super
else
- shell_out_with_systems_locale!("#{default_init_command} start")
+ shell_out!("#{default_init_command} start", default_env: false)
end
end
@@ -65,7 +64,7 @@ class Chef
if @new_resource.stop_command
super
else
- shell_out_with_systems_locale!("#{default_init_command} stop")
+ shell_out!("#{default_init_command} stop", default_env: false)
end
end
@@ -73,7 +72,7 @@ class Chef
if @new_resource.restart_command
super
elsif supports[:restart]
- shell_out_with_systems_locale!("#{default_init_command} restart")
+ shell_out!("#{default_init_command} restart", default_env: false)
else
stop_service
sleep 1
@@ -85,7 +84,7 @@ class Chef
if @new_resource.reload_command
super
elsif supports[:reload]
- shell_out_with_systems_locale!("#{default_init_command} reload")
+ shell_out!("#{default_init_command} reload", default_env: false)
end
end
end
diff --git a/lib/chef/provider/service/insserv.rb b/lib/chef/provider/service/insserv.rb
index 76b2ee7477..212ee8827b 100644
--- a/lib/chef/provider/service/insserv.rb
+++ b/lib/chef/provider/service/insserv.rb
@@ -1,6 +1,6 @@
#
# Author:: Bryan McLellan <btm@loftninjas.org>
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,27 +16,29 @@
# limitations under the License.
#
-require "chef/provider/service/init"
-require "chef/util/path_helper"
+require_relative "init"
+require_relative "../../util/path_helper"
class Chef
class Provider
class Service
class Insserv < Chef::Provider::Service::Init
- provides :service, platform_family: %w{debian rhel fedora suse} do |node|
- Chef::Platform::ServiceHelpers.service_resource_providers.include?(:insserv)
+ provides :service, platform_family: %w{debian rhel fedora suse amazon} do
+ insserv?
end
def self.supports?(resource, action)
- Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:initd)
+ service_script_exist?(:initd, resource.service_name)
end
def load_current_resource
super
# Look for a /etc/rc.*/SnnSERVICE link to signify that the service would be started in a runlevel
- if Dir.glob("/etc/rc**/S*#{Chef::Util::PathHelper.escape_glob_dir(current_resource.service_name)}").empty?
+ service_name = Chef::Util::PathHelper.escape_glob_dir(current_resource.service_name)
+
+ if Dir.glob("/etc/rc*/**/S*#{service_name}").empty?
current_resource.enabled false
else
current_resource.enabled true
@@ -45,12 +47,12 @@ class Chef
current_resource
end
- def enable_service()
+ def enable_service
shell_out!("/sbin/insserv -r -f #{new_resource.service_name}")
shell_out!("/sbin/insserv -d -f #{new_resource.service_name}")
end
- def disable_service()
+ def disable_service
shell_out!("/sbin/insserv -r -f #{new_resource.service_name}")
end
end
diff --git a/lib/chef/provider/service/invokercd.rb b/lib/chef/provider/service/invokercd.rb
index 9477afec48..17bedd0634 100644
--- a/lib/chef/provider/service/invokercd.rb
+++ b/lib/chef/provider/service/invokercd.rb
@@ -1,6 +1,6 @@
#
# Author:: AJ Christensen (<aj@hjksolutions.com>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/provider/service/init"
+require_relative "init"
class Chef
class Provider
class Service
class Invokercd < Chef::Provider::Service::Init
- provides :service, platform_family: "debian", override: true do |node|
- Chef::Platform::ServiceHelpers.service_resource_providers.include?(:invokercd)
+ provides :service, platform_family: "debian", override: true do
+ invokercd?
end
def self.supports?(resource, action)
- Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:initd)
+ service_script_exist?(:initd, resource.service_name)
end
def initialize(new_resource, run_context)
diff --git a/lib/chef/provider/service/macosx.rb b/lib/chef/provider/service/macosx.rb
index 648cd9748b..2152789a6e 100644
--- a/lib/chef/provider/service/macosx.rb
+++ b/lib/chef/provider/service/macosx.rb
@@ -1,6 +1,7 @@
#
# Author:: Igor Afonov <afonov@gmail.com>
# Copyright:: Copyright 2011-2016, Igor Afonov
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,19 +17,19 @@
# limitations under the License.
#
-require "etc"
-require "rexml/document"
-require "chef/resource/service"
-require "chef/resource/macosx_service"
-require "chef/provider/service/simple"
-require "chef/util/path_helper"
+require "etc" unless defined?(Etc)
+autoload :REXML, "rexml/document"
+require_relative "../../resource/service"
+require_relative "../../resource/macosx_service"
+require_relative "simple"
+require_relative "../../util/path_helper"
class Chef
class Provider
class Service
class Macosx < Chef::Provider::Service::Simple
- provides :macosx_service, os: "darwin"
+ provides :macosx_service
provides :service, os: "darwin"
def self.gather_plist_dirs
@@ -42,31 +43,28 @@ class Chef
PLIST_DIRS = gather_plist_dirs
- def this_version_or_newer?(this_version)
- Gem::Version.new(node["platform_version"]) >= Gem::Version.new(this_version)
- end
-
def load_current_resource
@current_resource = Chef::Resource::MacosxService.new(@new_resource.name)
@current_resource.service_name(@new_resource.service_name)
@plist_size = 0
- @plist = @new_resource.plist ? @new_resource.plist : find_service_plist
+ @plist = @new_resource.plist || find_service_plist
@service_label = find_service_label
- # LauchAgents should be loaded as the console user.
+ # LaunchAgents 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
+ @console_user = Etc.getpwuid(::File.stat("/dev/console").uid).name
+ logger.trace("#{new_resource} console_user: '#{@console_user}'")
+
+ @base_user_cmd = "su -l #{@console_user} -c"
+ logger.trace("#{new_resource} base_user_cmd: '#{@base_user_cmd}'")
+
+ # Default LaunchAgent session should be Aqua
@session_type = "Aqua" if @session_type.nil?
end
- Chef::Log.debug("#{new_resource} Plist: '#{@plist}' service_label: '#{@service_label}'")
+ logger.trace("#{new_resource} Plist: '#{@plist}' service_label: '#{@service_label}'")
set_service_status
@current_resource
@@ -83,7 +81,7 @@ class Chef
end
requirements.assert(:all_actions) do |a|
- a.assertion { ::File.exists?(@plist.to_s) }
+ a.assertion { ::File.exist?(@plist.to_s) }
a.failure_message Chef::Exceptions::Service,
"Could not find plist for #{@new_resource}"
end
@@ -107,7 +105,7 @@ class Chef
def start_service
if @current_resource.running
- Chef::Log.debug("#{@new_resource} already running, not starting")
+ logger.trace("#{@new_resource} already running, not starting")
else
if @new_resource.start_command
super
@@ -119,7 +117,7 @@ class Chef
def stop_service
unless @current_resource.running
- Chef::Log.debug("#{@new_resource} not running, not stopping")
+ logger.trace("#{@new_resource} not running, not stopping")
else
if @new_resource.stop_command
super
@@ -139,14 +137,23 @@ class Chef
end
end
- # On OS/X, enabling a service has the side-effect of starting it,
+ # On macOS, enabling a service has the side-effect of starting it,
# and disabling a service has the side-effect of stopping it.
#
- # This makes some sense on OS/X since launchctl is an "init"-style
+ # This makes some sense on macOS since launchctl is an "init"-style
# supervisor that will restart daemons that are crashing, etc.
+ #
+ # FIXME: Does this make any sense at all? The difference between enabled and
+ # running as state would seem to only be useful for completely broken
+ # services (enabled, not restarting, but not running => totally broken?).
+ #
+ # It seems like otherwise :enable is equivalent to :start, and :disable is
+ # equivalent to :stop? But just with strangely different behavior in the
+ # face of a broken service?
+ #
def enable_service
if @current_resource.enabled
- Chef::Log.debug("#{@new_resource} already enabled, not enabling")
+ logger.trace("#{@new_resource} already enabled, not enabling")
else
load_service
end
@@ -154,7 +161,7 @@ class Chef
def disable_service
unless @current_resource.enabled
- Chef::Log.debug("#{@new_resource} not enabled, not disabling")
+ logger.trace("#{@new_resource} not enabled, not disabling")
else
unload_service
end
@@ -173,15 +180,15 @@ class Chef
def shell_out_as_user(cmd)
if @console_user
- shell_out_with_systems_locale("#{@base_user_cmd} '#{cmd}'")
+ shell_out("#{@base_user_cmd} '#{cmd}'", default_env: false)
else
- shell_out_with_systems_locale(cmd)
+ shell_out(cmd, default_env: false)
end
end
def set_service_status
- return if @plist == nil || @service_label.to_s.empty?
+ return if @plist.nil? || @service_label.to_s.empty?
cmd = "launchctl list #{@service_label}"
res = shell_out_as_user(cmd)
@@ -197,8 +204,8 @@ class Chef
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}")
+ @current_resource.running(pid.to_i != 0)
+ logger.trace("Current PID for #{@service_label} is #{pid}")
end
end
else
@@ -214,7 +221,7 @@ class Chef
return nil if @plist.nil?
# Plist must exist by this point
- raise Chef::Exceptions::FileNotFound, "Cannot find #{@plist}!" unless ::File.exists?(@plist)
+ raise Chef::Exceptions::FileNotFound, "Cannot find #{@plist}!" unless ::File.exist?(@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
@@ -223,8 +230,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_with_systems_locale!(
- "plutil -convert xml1 -o - #{@plist}"
+ plist_xml = shell_out!(
+ "plutil -convert xml1 -o - #{@plist}",
+ default_env: false
).stdout
plist_doc = REXML::Document.new(plist_xml)
diff --git a/lib/chef/provider/service/openbsd.rb b/lib/chef/provider/service/openbsd.rb
index c60bbf170c..2b484f9fc8 100644
--- a/lib/chef/provider/service/openbsd.rb
+++ b/lib/chef/provider/service/openbsd.rb
@@ -16,10 +16,8 @@
# limitations under the License.
#
-require "chef/mixin/command"
-require "chef/mixin/shell_out"
-require "chef/provider/service/init"
-require "chef/resource/service"
+require_relative "init"
+require_relative "../../resource/service"
class Chef
class Provider
@@ -28,12 +26,10 @@ class Chef
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".freeze
+ RC_CONF_LOCAL_PATH = "/etc/rc.conf.local".freeze
def initialize(new_resource, run_context)
super
@@ -49,7 +45,7 @@ class Chef
@current_resource = Chef::Resource::Service.new(new_resource.name)
current_resource.service_name(new_resource.service_name)
- Chef::Log.debug("#{current_resource} found at #{init_command}")
+ logger.trace("#{current_resource} found at #{init_command}")
determine_current_status!
determine_enabled_status!
@@ -72,14 +68,14 @@ class Chef
end
requirements.assert(:start, :enable, :reload, :restart) do |a|
- a.assertion { init_command && builtin_service_enable_variable_name != nil }
+ a.assertion { init_command && !builtin_service_enable_variable_name.nil? }
a.failure_message Chef::Exceptions::Service, "Could not find the service name in #{init_command} and rcvar"
# No recovery in whyrun mode - the init file is present but not correct.
end
end
def enable_service
- if !is_enabled?
+ unless is_enabled?
if is_builtin?
if is_enabled_by_default?
update_rcl rc_conf_local.sub(/^#{Regexp.escape(builtin_service_enable_variable_name)}=.*/, "")
@@ -92,10 +88,10 @@ class Chef
old_services_list = rc_conf_local.match(/^pkg_scripts="(.*)"/)
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 =~ /^pkg_scripts="(.*)"/
- new_rcl = rc_conf_local.sub(/^pkg_scripts="(.*)"/, "pkg_scripts=\"#{new_services_list.join(' ')}\"")
+ if /^pkg_scripts="(.*)"/.match?(rc_conf_local)
+ new_rcl = rc_conf_local.sub(/^pkg_scripts="(.*)"/, "pkg_scripts=\"#{new_services_list.join(" ")}\"")
else
- new_rcl = rc_conf_local + "\n" + "pkg_scripts=\"#{new_services_list.join(' ')}\"\n"
+ new_rcl = rc_conf_local + "\n" + "pkg_scripts=\"#{new_services_list.join(" ")}\"\n"
end
update_rcl new_rcl
end
@@ -117,7 +113,7 @@ class Chef
old_list = rc_conf_local.match(/^pkg_scripts="(.*)"/)
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(' ')}")
+ update_rcl rc_conf_local.sub(/^pkg_scripts="(.*)"/, pkg_scripts = (new_list.join(" ")).to_s)
end
end
end
@@ -133,7 +129,7 @@ class Chef
end
def update_rcl(value)
- FileUtils.touch RC_CONF_LOCAL_PATH if !::File.exists? RC_CONF_LOCAL_PATH
+ FileUtils.touch RC_CONF_LOCAL_PATH unless ::File.exist? RC_CONF_LOCAL_PATH
::File.write(RC_CONF_LOCAL_PATH, value)
@rc_conf_local = value
end
@@ -159,7 +155,7 @@ class Chef
result = false
var_name = builtin_service_enable_variable_name
if var_name
- if rc_conf =~ /^#{Regexp.escape(var_name)}=(.*)/
+ if /^#{Regexp.escape(var_name)}=(.*)/.match?(rc_conf)
result = true
end
end
@@ -171,7 +167,7 @@ class Chef
var_name = builtin_service_enable_variable_name
if var_name
if m = rc_conf.match(/^#{Regexp.escape(var_name)}=(.*)/)
- if !(m[1] =~ /"?[Nn][Oo]"?/)
+ unless /"?[Nn][Oo]"?/.match?(m[1])
result = true
end
end
@@ -187,12 +183,12 @@ class Chef
if var_name
if m = rc_conf_local.match(/^#{Regexp.escape(var_name)}=(.*)/)
@enabled_state_found = true
- if !(m[1] =~ /"?[Nn][Oo]"?/) # e.g. looking for httpd_flags=NO
+ unless /"?[Nn][Oo]"?/.match?(m[1]) # e.g. looking for httpd_flags=NO
result = true
end
end
end
- if !@enabled_state_found
+ unless @enabled_state_found
result = is_enabled_by_default?
end
else
diff --git a/lib/chef/provider/service/redhat.rb b/lib/chef/provider/service/redhat.rb
index 200a2d3400..14b55bef85 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 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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/provider/service/init"
+require_relative "init"
class Chef
class Provider
@@ -28,15 +28,15 @@ class Chef
# @api private
attr_accessor :current_run_levels
- provides :service, platform_family: %w{rhel fedora suse} do |node|
- Chef::Platform::ServiceHelpers.service_resource_providers.include?(:redhat)
+ provides :service, platform_family: "rpm_based" do
+ redhatrcd?
end
- CHKCONFIG_ON = /\d:on/
- CHKCONFIG_MISSING = /No such/
+ CHKCONFIG_ON = /\d:on/.freeze
+ CHKCONFIG_MISSING = /No such/.freeze
def self.supports?(resource, action)
- Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:initd)
+ service_script_exist?(:initd, resource.service_name)
end
def initialize(new_resource, run_context)
@@ -56,7 +56,7 @@ class Chef
requirements.assert(:all_actions) do |a|
chkconfig_file = "/sbin/chkconfig"
- a.assertion { ::File.exists? chkconfig_file }
+ a.assertion { ::File.exist? chkconfig_file }
a.failure_message Chef::Exceptions::Service, "#{chkconfig_file} does not exist!"
end
@@ -80,14 +80,14 @@ class Chef
super
- if ::File.exists?("/sbin/chkconfig")
- chkconfig = shell_out!("/sbin/chkconfig --list #{current_resource.service_name}", :returns => [0, 1])
+ if ::File.exist?("/sbin/chkconfig")
+ chkconfig = shell_out!("/sbin/chkconfig --list #{current_resource.service_name}", returns: [0, 1])
unless run_levels.nil? || run_levels.empty?
all_levels_match = true
- chkconfig.stdout.split(/\s+/)[1..-1].each do |level|
+ chkconfig.stdout.split(/\s+/)[1..].each do |level|
index = level.split(":").first
status = level.split(":").last
- if level =~ CHKCONFIG_ON
+ if CHKCONFIG_ON.match?(level)
@current_run_levels << index.to_i
all_levels_match = false unless run_levels.include?(index.to_i)
else
@@ -106,18 +106,18 @@ class Chef
# @api private
def levels
- (run_levels.nil? || run_levels.empty?) ? "" : "--level #{run_levels.join('')} "
+ (run_levels.nil? || run_levels.empty?) ? "" : "--level #{run_levels.join("")} "
end
- def enable_service()
+ def enable_service
unless run_levels.nil? || 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?
+ 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()
+ def disable_service
shell_out! "/sbin/chkconfig #{levels}#{new_resource.service_name} off"
end
end
diff --git a/lib/chef/provider/service/simple.rb b/lib/chef/provider/service/simple.rb
index d75e85989f..0d1a9ae786 100644
--- a/lib/chef/provider/service/simple.rb
+++ b/lib/chef/provider/service/simple.rb
@@ -16,9 +16,8 @@
# limitations under the License.
#
-require "chef/provider/service"
-require "chef/resource/service"
-require "chef/mixin/command"
+require_relative "../service"
+require_relative "../../resource/service"
class Chef
class Provider
@@ -41,10 +40,6 @@ class Chef
@current_resource
end
- def whyrun_supported?
- true
- end
-
def shared_resource_requirements
super
requirements.assert(:all_actions) do |a|
@@ -78,7 +73,8 @@ class Chef
requirements.assert(:all_actions) do |a|
a.assertion do
@new_resource.status_command || supports[:status] ||
- (!ps_cmd.nil? && !ps_cmd.empty?) end
+ (!ps_cmd.nil? && !ps_cmd.empty?)
+ end
a.failure_message Chef::Exceptions::Service, "#{@new_resource} could not determine how to inspect the process table, please set this node's 'command.ps' attribute"
end
requirements.assert(:all_actions) do |a|
@@ -88,16 +84,16 @@ class Chef
end
def start_service
- shell_out_with_systems_locale!(@new_resource.start_command)
+ shell_out!(@new_resource.start_command, default_env: false)
end
def stop_service
- shell_out_with_systems_locale!(@new_resource.stop_command)
+ shell_out!(@new_resource.stop_command, default_env: false)
end
def restart_service
if @new_resource.restart_command
- shell_out_with_systems_locale!(@new_resource.restart_command)
+ shell_out!(@new_resource.restart_command, default_env: false)
else
stop_service
sleep 1
@@ -106,35 +102,35 @@ class Chef
end
def reload_service
- shell_out_with_systems_locale!(@new_resource.reload_command)
+ shell_out!(@new_resource.reload_command, default_env: false)
end
protected
def determine_current_status!
if @new_resource.status_command
- Chef::Log.debug("#{@new_resource} you have specified a status command, running..")
+ logger.trace("#{@new_resource} you have specified a status command, running..")
begin
if shell_out(@new_resource.status_command).exitstatus == 0
@current_resource.running true
- Chef::Log.debug("#{@new_resource} is running")
+ logger.trace("#{@new_resource} is running")
end
rescue Mixlib::ShellOut::ShellCommandFailed, SystemCallError
- # ShellOut sometimes throws different types of Exceptions than ShellCommandFailed.
- # Temporarily catching different types of exceptions here until we get Shellout fixed.
- # TODO: Remove the line before one we get the ShellOut fix.
+ # ShellOut sometimes throws different types of Exceptions than ShellCommandFailed.
+ # Temporarily catching different types of exceptions here until we get Shellout fixed.
+ # TODO: Remove the line before one we get the ShellOut fix.
@status_load_success = false
@current_resource.running false
nil
end
elsif supports[:status]
- Chef::Log.debug("#{@new_resource} supports status, running")
+ logger.trace("#{@new_resource} supports status, running")
begin
if shell_out("#{default_init_command} status").exitstatus == 0
@current_resource.running true
- Chef::Log.debug("#{@new_resource} is running")
+ logger.trace("#{@new_resource} is running")
end
# ShellOut sometimes throws different types of Exceptions than ShellCommandFailed.
# Temporarily catching different types of exceptions here until we get Shellout fixed.
@@ -145,9 +141,9 @@ class Chef
nil
end
else
- Chef::Log.debug "#{@new_resource} falling back to process table inspection"
+ logger.trace "#{@new_resource} falling back to process table inspection"
r = Regexp.new(@new_resource.pattern)
- Chef::Log.debug "#{@new_resource} attempting to match '#{@new_resource.pattern}' (#{r.inspect}) against process list"
+ logger.trace "#{@new_resource} attempting to match '#{@new_resource.pattern}' (#{r.inspect}) against process list"
begin
shell_out!(ps_cmd).stdout.each_line do |line|
if r.match(line)
@@ -157,7 +153,7 @@ class Chef
end
@current_resource.running false unless @current_resource.running
- Chef::Log.debug "#{@new_resource} running: #{@current_resource.running}"
+ logger.trace "#{@new_resource} running: #{@current_resource.running}"
# ShellOut sometimes throws different types of Exceptions than ShellCommandFailed.
# Temporarily catching different types of exceptions here until we get Shellout fixed.
# TODO: Remove the line before one we get the ShellOut fix.
@@ -168,6 +164,7 @@ class Chef
end
def ps_cmd
+ # XXX: magic attributes are a shitty api, need something better here and deprecate this attribute
@run_context.node[:command] && @run_context.node[:command][:ps]
end
end
diff --git a/lib/chef/provider/service/solaris.rb b/lib/chef/provider/service/solaris.rb
index 868b3e1ac1..fa4a17bd0c 100644
--- a/lib/chef/provider/service/solaris.rb
+++ b/lib/chef/provider/service/solaris.rb
@@ -1,6 +1,6 @@
#
# Author:: Toomas Pelberg (<toomasp@gmx.net>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,8 @@
# limitations under the License.
#
-require "chef/provider/service"
-require "chef/resource/service"
-require "chef/mixin/command"
+require_relative "../service"
+require_relative "../../resource/service"
class Chef
class Provider
@@ -32,7 +31,7 @@ class Chef
super
@init_command = "/usr/sbin/svcadm"
@status_command = "/bin/svcs"
- @maintenace = false
+ @maintenance = false
end
def load_current_resource
@@ -55,12 +54,16 @@ class Chef
end
def enable_service
+ # Running service status to update maintenance status to invoke svcadm clear
+ service_status
shell_out!(default_init_command, "clear", @new_resource.service_name) if @maintenance
- shell_out!(default_init_command, "enable", "-s", @new_resource.service_name)
+ enable_flags = [ "-s", @new_resource.options ].flatten.compact
+ shell_out!(default_init_command, "enable", *enable_flags, @new_resource.service_name)
end
def disable_service
- shell_out!(default_init_command, "disable", "-s", @new_resource.service_name)
+ disable_flags = [ "-s", @new_resource.options ].flatten.compact
+ shell_out!(default_init_command, "disable", *disable_flags, @new_resource.service_name)
end
alias_method :stop_service, :disable_service
@@ -73,11 +76,11 @@ class Chef
def restart_service
## svcadm restart doesn't supports sync(-s) option
disable_service
- return enable_service
+ enable_service
end
def service_status
- cmd = shell_out!(@status_command, "-l", @current_resource.service_name, :returns => [0, 1])
+ cmd = shell_out!(@status_command, "-l", @current_resource.service_name, returns: [0, 1])
# Example output
# $ svcs -l rsyslog
# fmri svc:/application/rsyslog:default
@@ -92,6 +95,9 @@ class Chef
# dependency require_all/error svc:/milestone/multi-user:default (online)
# $
+ # Set the default value for maintenance
+ @maintenance = false
+
# load output into hash
status = {}
cmd.stdout.each_line do |line|
@@ -100,7 +106,6 @@ class Chef
end
# check service state
- @maintenance = false
case status["state"]
when "online"
@current_resource.enabled(true)
diff --git a/lib/chef/provider/service/systemd.rb b/lib/chef/provider/service/systemd.rb
index 712f0f60c3..9b0cf3abd8 100644
--- a/lib/chef/provider/service/systemd.rb
+++ b/lib/chef/provider/service/systemd.rb
@@ -1,7 +1,7 @@
#
# Author:: Stephen Haynes (<sh@nomitor.com>)
# Author:: Davide Cavalca (<dcavalca@fb.com>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,22 +17,23 @@
# limitations under the License.
#
-require "chef/resource/service"
-require "chef/provider/service/simple"
-require "chef/mixin/which"
+require_relative "../../resource/service"
+require_relative "simple"
+require_relative "../../mixin/which"
+require "shellwords" unless defined?(Shellwords)
class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
include Chef::Mixin::Which
- provides :service, os: "linux" do |node|
- Chef::Platform::ServiceHelpers.service_resource_providers.include?(:systemd)
+ provides :service, os: "linux", target_mode: true do |node|
+ systemd?
end
attr_accessor :status_check_success
def self.supports?(resource, action)
- Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:systemd)
+ service_script_exist?(:systemd, resource.service_name)
end
def load_current_resource
@@ -41,7 +42,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
@status_check_success = true
if new_resource.status_command
- Chef::Log.debug("#{new_resource} you have specified a status command, running..")
+ logger.trace("#{new_resource} you have specified a status command, running..")
unless shell_out(new_resource.status_command).error?
current_resource.running(true)
@@ -50,6 +51,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
current_resource.running(false)
current_resource.enabled(false)
current_resource.masked(false)
+ current_resource.indirect(false)
end
else
current_resource.running(is_active?)
@@ -57,12 +59,12 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
current_resource.enabled(is_enabled?)
current_resource.masked(is_masked?)
+ current_resource.indirect(is_indirect?)
current_resource
end
# systemd supports user services just fine
- def user_services_requirements
- end
+ def user_services_requirements; end
def define_resource_requirements
shared_resource_requirements
@@ -74,14 +76,40 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
end
end
+ def systemd_service_status
+ @systemd_service_status ||= begin
+ # Collect all the status information for a service and returns it at once
+ options, args = get_systemctl_options_args
+ s = shell_out!(systemctl_path, args, "show", "-p", "UnitFileState", "-p", "ActiveState", new_resource.service_name, options)
+ # e.g. /bin/systemctl --system show -p UnitFileState -p ActiveState sshd.service
+ # Returns something like:
+ # ActiveState=active
+ # UnitFileState=enabled
+ status = {}
+ s.stdout.each_line do |line|
+ k, v = line.strip.split("=")
+ status[k] = v
+ end
+
+ # Assert requisite keys exist
+ unless status.key?("UnitFileState") && status.key?("ActiveState")
+ raise Chef::Exceptions::Service, "'#{systemctl_path} show' not reporting status for #{new_resource.service_name}!"
+ end
+
+ status
+ end
+ end
+
def get_systemctl_options_args
if new_resource.user
- uid = node["etc"]["passwd"][new_resource.user]["uid"]
+ raise NotImplementedError, "#{new_resource} does not support the user property on a target_mode host (yet)" if Chef::Config.target_mode?
+
+ uid = Etc.getpwnam(new_resource.user).uid
options = {
- :environment => {
+ environment: {
"DBUS_SESSION_BUS_ADDRESS" => "unix:path=/run/user/#{uid}/bus",
},
- :user => new_resource.user,
+ user: new_resource.user,
}
args = "--user"
else
@@ -89,31 +117,31 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
args = "--system"
end
- return options, args
+ [options, args]
end
def start_service
if current_resource.running
- Chef::Log.debug("#{new_resource} already running, not starting")
+ logger.trace("#{new_resource} already running, not starting")
else
if new_resource.start_command
super
else
options, args = get_systemctl_options_args
- shell_out_with_systems_locale!("#{systemctl_path} #{args} start #{new_resource.service_name}", options)
+ shell_out!(systemctl_path, args, "start", new_resource.service_name, default_env: false, **options)
end
end
end
def stop_service
unless current_resource.running
- Chef::Log.debug("#{new_resource} not running, not stopping")
+ logger.trace("#{new_resource} not running, not stopping")
else
if new_resource.stop_command
super
else
options, args = get_systemctl_options_args
- shell_out_with_systems_locale!("#{systemctl_path} #{args} stop #{new_resource.service_name}", options)
+ shell_out!(systemctl_path, args, "stop", new_resource.service_name, default_env: false, **options)
end
end
end
@@ -123,7 +151,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
super
else
options, args = get_systemctl_options_args
- shell_out_with_systems_locale!("#{systemctl_path} #{args} restart #{new_resource.service_name}", options)
+ shell_out!(systemctl_path, args, "restart", new_resource.service_name, default_env: false, **options)
end
end
@@ -133,7 +161,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
else
if current_resource.running
options, args = get_systemctl_options_args
- shell_out_with_systems_locale!("#{systemctl_path} #{args} reload #{new_resource.service_name}", options)
+ shell_out!(systemctl_path, args, "reload", new_resource.service_name, default_env: false, **options)
else
start_service
end
@@ -141,39 +169,53 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
end
def enable_service
+ if current_resource.masked || current_resource.indirect
+ logger.trace("#{new_resource} cannot be enabled: it is masked or indirect")
+ return
+ end
options, args = get_systemctl_options_args
- shell_out!("#{systemctl_path} #{args} enable #{new_resource.service_name}", options)
+ shell_out!(systemctl_path, args, "enable", new_resource.service_name, **options)
end
def disable_service
+ if current_resource.masked || current_resource.indirect
+ logger.trace("#{new_resource} cannot be disabled: it is masked or indirect")
+ return
+ end
options, args = get_systemctl_options_args
- shell_out!("#{systemctl_path} #{args} disable #{new_resource.service_name}", options)
+ shell_out!(systemctl_path, args, "disable", new_resource.service_name, **options)
end
def mask_service
options, args = get_systemctl_options_args
- shell_out!("#{systemctl_path} #{args} mask #{new_resource.service_name}", options)
+ shell_out!(systemctl_path, args, "mask", new_resource.service_name, **options)
end
def unmask_service
options, args = get_systemctl_options_args
- shell_out!("#{systemctl_path} #{args} unmask #{new_resource.service_name}", options)
+ shell_out!(systemctl_path, args, "unmask", new_resource.service_name, **options)
end
def is_active?
- options, args = get_systemctl_options_args
- shell_out("#{systemctl_path} #{args} is-active #{new_resource.service_name} --quiet", options).exitstatus == 0
+ # Note: "activating" is not active (as with type=notify or a oneshot)
+ systemd_service_status["ActiveState"] == "active"
end
def is_enabled?
- options, args = get_systemctl_options_args
- shell_out("#{systemctl_path} #{args} is-enabled #{new_resource.service_name} --quiet", options).exitstatus == 0
+ # See https://github.com/systemd/systemd/blob/master/src/systemctl/systemctl-is-enabled.c
+ # Note: enabled-runtime is excluded because this is volatile, and the state of enabled-runtime
+ # specifically means that the service is not enabled
+ %w{enabled static generated alias indirect}.include?(systemd_service_status["UnitFileState"])
+ end
+
+ def is_indirect?
+ systemd_service_status["UnitFileState"] == "indirect"
end
def is_masked?
- options, args = get_systemctl_options_args
- s = shell_out("#{systemctl_path} #{args} is-enabled #{new_resource.service_name}", options)
- s.exitstatus != 0 && s.stdout.include?("masked")
+ # Note: masked-runtime is excluded, because runtime is volatile, and
+ # because masked-runtime is not masked.
+ systemd_service_status["UnitFileState"] == "masked"
end
private
diff --git a/lib/chef/provider/service/upstart.rb b/lib/chef/provider/service/upstart.rb
index 6e2a3b6473..2b9e304160 100644
--- a/lib/chef/provider/service/upstart.rb
+++ b/lib/chef/provider/service/upstart.rb
@@ -16,29 +16,32 @@
# limitations under the License.
#
-require "chef/resource/service"
-require "chef/provider/service/simple"
-require "chef/mixin/command"
-require "chef/util/file_edit"
+require_relative "../../resource/service"
+require_relative "simple"
+require_relative "../../util/file_edit"
class Chef
class Provider
class Service
class Upstart < Chef::Provider::Service::Simple
- provides :service, platform_family: "debian", override: true do |node|
- Chef::Platform::ServiceHelpers.service_resource_providers.include?(:upstart)
+ # to maintain a local state of service across restart's internal calls
+ attr_accessor :upstart_service_running
+
+ provides :service, platform_family: "debian", override: true do
+ upstart?
end
- UPSTART_STATE_FORMAT = /\S+ \(?(start|stop)?\)? ?[\/ ](\w+)/
+ UPSTART_STATE_FORMAT = %r{\S+ \(?(start|stop)?\)? ?[/ ](\w+)}.freeze
+ # Returns true if the configs for the service name has upstart variable
def self.supports?(resource, action)
- Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:upstart)
+ service_script_exist?(:upstart, resource.service_name)
end
# Upstart does more than start or stop a service, creating multiple 'states' [1] that a service can be in.
# In chef, when we ask a service to start, we expect it to have started before performing the next step
- # since we have top down dependencies. Which is to say we may follow witha resource next that requires
+ # since we have top down dependencies. Which is to say we may follow with a resource next that requires
# that service to be running. According to [2] we can trust that sending a 'goal' such as start will not
# return until that 'goal' is reached, or some error has occurred.
#
@@ -48,6 +51,7 @@ class Chef
def initialize(new_resource, run_context)
# TODO: re-evaluate if this is needed after integrating cookbook fix
raise ArgumentError, "run_context cannot be nil" unless run_context
+
super
run_context.node
@@ -79,7 +83,7 @@ class Chef
# Do not call super, only call shared requirements
shared_resource_requirements
requirements.assert(:all_actions) do |a|
- if !@command_success
+ unless @command_success
whyrun_msg = if @new_resource.status_command
"Provided status command #{@new_resource.status_command} failed."
else
@@ -106,42 +110,42 @@ class Chef
# We do not support searching for a service via ps when using upstart since status is a native
# upstart function. We will however support status_command in case someone wants to do something special.
if @new_resource.status_command
- Chef::Log.debug("#{@new_resource} you have specified a status command, running..")
+ logger.trace("#{@new_resource} you have specified a status command, running..")
begin
if shell_out!(@new_resource.status_command).exitstatus == 0
- @current_resource.running true
+ @upstart_service_running = true
end
rescue
@command_success = false
- @current_resource.running false
+ @upstart_service_running = false
nil
end
else
begin
if upstart_goal_state == "start"
- @current_resource.running true
+ @upstart_service_running = true
else
- @current_resource.running false
+ @upstart_service_running = false
end
rescue Chef::Exceptions::Exec
@command_success = false
- @current_resource.running false
+ @upstart_service_running = false
nil
end
end
# Get enabled/disabled state by reading job configuration file
- if ::File.exists?("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
- Chef::Log.debug("#{@new_resource} found #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
+ if ::File.exist?("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
+ logger.trace("#{@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|
while line = file.gets
case line
when /^start on/
- Chef::Log.debug("#{@new_resource} enabled: #{line.chomp}")
+ logger.trace("#{@new_resource} enabled: #{line.chomp}")
@current_resource.enabled true
break
when /^#start on/
- Chef::Log.debug("#{@new_resource} disabled: #{line.chomp}")
+ logger.trace("#{@new_resource} disabled: #{line.chomp}")
@current_resource.enabled false
break
end
@@ -149,39 +153,44 @@ class Chef
end
else
@config_file_found = false
- Chef::Log.debug("#{@new_resource} did not find #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
+ logger.trace("#{@new_resource} did not find #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
@current_resource.enabled false
end
+ @current_resource.running @upstart_service_running
@current_resource
end
def start_service
# Calling start on a service that is already started will return 1
# Our 'goal' when we call start is to ensure the service is started
- if @current_resource.running
- Chef::Log.debug("#{@new_resource} already running, not starting")
+ if @upstart_service_running
+ logger.trace("#{@new_resource} already running, not starting")
else
if @new_resource.start_command
super
else
- shell_out_with_systems_locale!("/sbin/start #{@job}")
+ shell_out!("/sbin/start #{@job}", default_env: false)
end
end
+
+ @upstart_service_running = true
end
def stop_service
# Calling stop on a service that is already stopped will return 1
# Our 'goal' when we call stop is to ensure the service is stopped
- unless @current_resource.running
- Chef::Log.debug("#{@new_resource} not running, not stopping")
+ unless @upstart_service_running
+ logger.trace("#{@new_resource} not running, not stopping")
else
if @new_resource.stop_command
super
else
- shell_out_with_systems_locale!("/sbin/stop #{@job}")
+ shell_out!("/sbin/stop #{@job}", default_env: false)
end
end
+
+ @upstart_service_running = false
end
def restart_service
@@ -189,13 +198,19 @@ class Chef
super
# Upstart always provides restart functionality so we don't need to mimic it with stop/sleep/start.
# Older versions of upstart would fail on restart if the service was currently stopped, check for that. LP:430883
+ # But for safe working of latest upstart job config being loaded, 'restart' can't be used as per link
+ # http://upstart.ubuntu.com/cookbook/#restart (it doesn't uses latest jon config from disk but retains old)
else
- if @current_resource.running
- shell_out_with_systems_locale!("/sbin/restart #{@job}")
+ if @upstart_service_running
+ stop_service
+ sleep 1
+ start_service
else
start_service
end
end
+
+ @upstart_service_running = true
end
def reload_service
@@ -203,21 +218,23 @@ class Chef
super
else
# upstart >= 0.6.3-4 supports reload (HUP)
- shell_out_with_systems_locale!("/sbin/reload #{@job}")
+ shell_out!("/sbin/reload #{@job}", default_env: false)
end
+
+ @upstart_service_running = true
end
# https://bugs.launchpad.net/upstart/+bug/94065
def enable_service
- Chef::Log.debug("#{@new_resource} upstart lacks inherent support for enabling services, editing job config file")
+ logger.trace("#{@new_resource} upstart lacks inherent support for enabling services, editing job config file")
conf = Chef::Util::FileEdit.new("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
conf.search_file_replace(/^#start on/, "start on")
conf.write_file
end
def disable_service
- Chef::Log.debug("#{@new_resource} upstart lacks inherent support for disabling services, editing job config file")
+ logger.trace("#{@new_resource} upstart lacks inherent support for disabling services, editing job config file")
conf = Chef::Util::FileEdit.new("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
conf.search_file_replace(/^start on/, "#start on")
conf.write_file
@@ -225,17 +242,16 @@ class Chef
def upstart_goal_state
command = "/sbin/status #{@job}"
- status = popen4(command) do |pid, stdin, stdout, stderr|
- stdout.each_line do |line|
- # service goal/state
- # OR
- # service (instance) goal/state
- # OR
- # service (goal) state
- line =~ UPSTART_STATE_FORMAT
- data = Regexp.last_match
- return data[1]
- end
+ so = shell_out(command)
+ so.stdout.each_line do |line|
+ # service goal/state
+ # OR
+ # service (instance) goal/state
+ # OR
+ # service (goal) state
+ line =~ UPSTART_STATE_FORMAT
+ data = Regexp.last_match
+ return data[1]
end
end
diff --git a/lib/chef/provider/service/windows.rb b/lib/chef/provider/service/windows.rb
index 9bfd9238cd..98aad4fe29 100644
--- a/lib/chef/provider/service/windows.rb
+++ b/lib/chef/provider/service/windows.rb
@@ -2,7 +2,7 @@
# Author:: Nuo Yan <nuo@chef.io>
# Author:: Bryan McLellan <btm@loftninjas.org>
# Author:: Seth Chisamore <schisamo@chef.io>
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,97 +18,94 @@
# limitations under the License.
#
-require "chef/provider/service/simple"
-if RUBY_PLATFORM =~ /mswin|mingw32|windows/
- require "chef/win32/error"
+require_relative "simple"
+require_relative "../../win32_service_constants"
+if RUBY_PLATFORM.match?(/mswin|mingw32|windows/)
+ require_relative "../../win32/error"
require "win32/service"
end
class Chef::Provider::Service::Windows < Chef::Provider::Service
provides :service, os: "windows"
- provides :windows_service, os: "windows"
+ provides :windows_service
include Chef::Mixin::ShellOut
include Chef::ReservedNames::Win32::API::Error rescue LoadError
+ include Chef::Win32ServiceConstants
- #Win32::Service.get_start_type
- AUTO_START = "auto start"
- MANUAL = "demand start"
- DISABLED = "disabled"
+ # Win32::Service.get_start_type
+ AUTO_START = "auto start".freeze
+ MANUAL = "demand start".freeze
+ DISABLED = "disabled".freeze
- #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"
+ # Win32::Service.get_current_state
+ RUNNING = "running".freeze
+ STOPPED = "stopped".freeze
+ CONTINUE_PENDING = "continue pending".freeze
+ PAUSE_PENDING = "pause pending".freeze
+ PAUSED = "paused".freeze
+ START_PENDING = "start pending".freeze
+ STOP_PENDING = "stop pending".freeze
- TIMEOUT = 60
-
- SERVICE_RIGHT = "SeServiceLogonRight"
-
- def whyrun_supported?
- false
- end
+ SERVICE_RIGHT = "SeServiceLogonRight".freeze
def load_current_resource
- @current_resource = Chef::Resource::WindowsService.new(@new_resource.name)
- @current_resource.service_name(@new_resource.service_name)
- @current_resource.running(current_state == RUNNING)
- Chef::Log.debug "#{@new_resource} running: #{@current_resource.running}"
- case current_start_type
- when AUTO_START
- @current_resource.enabled(true)
- when DISABLED
- @current_resource.enabled(false)
+ @current_resource = Chef::Resource::WindowsService.new(new_resource.name)
+ current_resource.service_name(new_resource.service_name)
+
+ if Win32::Service.exists?(current_resource.service_name)
+ current_resource.running(current_state == RUNNING)
+ logger.trace "#{new_resource} running: #{current_resource.running}"
+ case current_startup_type
+ when :automatic
+ current_resource.enabled(true)
+ when :disabled
+ current_resource.enabled(false)
+ end
+ logger.trace "#{new_resource} enabled: #{current_resource.enabled}"
+
+ config_info = Win32::Service.config_info(current_resource.service_name)
+ current_resource.service_type(get_service_type(config_info.service_type)) if config_info.service_type
+ current_resource.startup_type(start_type_to_sym(config_info.start_type)) if config_info.start_type
+ current_resource.error_control(get_error_control(config_info.error_control)) if config_info.error_control
+ current_resource.binary_path_name(config_info.binary_path_name) if config_info.binary_path_name
+ current_resource.load_order_group(config_info.load_order_group) if config_info.load_order_group
+ current_resource.dependencies(config_info.dependencies) if config_info.dependencies
+ current_resource.run_as_user(config_info.service_start_name) if config_info.service_start_name
+ current_resource.display_name(config_info.display_name) if config_info.display_name
+ current_resource.delayed_start(current_delayed_start) if current_delayed_start
end
- Chef::Log.debug "#{@new_resource} enabled: #{@current_resource.enabled}"
- @current_resource
+
+ current_resource
end
def start_service
if Win32::Service.exists?(@new_resource.service_name)
- # reconfiguration is idempotent, so just do it.
- new_config = {
- service_name: @new_resource.service_name,
- service_start_name: @new_resource.run_as_user,
- password: @new_resource.run_as_password,
- }.reject { |k, v| v.nil? || v.length == 0 }
-
- Win32::Service.configure(new_config)
- Chef::Log.info "#{@new_resource} configured with #{new_config.inspect}"
-
- if new_config.has_key?(: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
+ configure_service_run_as_properties
state = current_state
if state == RUNNING
- Chef::Log.debug "#{@new_resource} already started - nothing to do"
+ logger.trace "#{@new_resource} already started - nothing to do"
elsif state == START_PENDING
- Chef::Log.debug "#{@new_resource} already sent start signal - waiting for start"
+ logger.trace "#{@new_resource} already sent start signal - waiting for start"
wait_for_state(RUNNING)
elsif state == STOPPED
if @new_resource.start_command
- Chef::Log.debug "#{@new_resource} starting service using the given start_command"
+ logger.trace "#{@new_resource} starting service using the given start_command"
shell_out!(@new_resource.start_command)
else
spawn_command_thread do
- begin
- Win32::Service.start(@new_resource.service_name)
- rescue SystemCallError => ex
- if ex.errno == ERROR_SERVICE_LOGON_FAILED
- Chef::Log.error ex.message
- raise Chef::Exceptions::Service,
+
+ Win32::Service.start(@new_resource.service_name)
+ rescue SystemCallError => ex
+ if ex.errno == ERROR_SERVICE_LOGON_FAILED
+ logger.error ex.message
+ raise Chef::Exceptions::Service,
"Service #{@new_resource} did not start due to a logon failure (error #{ERROR_SERVICE_LOGON_FAILED}): possibly the specified user '#{@new_resource.run_as_user}' does not have the 'log on as a service' privilege, or the password is incorrect."
- else
- raise ex
- end
+ else
+ raise ex
end
+
end
wait_for_state(RUNNING)
end
@@ -117,7 +114,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
raise Chef::Exceptions::Service, "Service #{@new_resource} can't be started from state [#{state}]"
end
else
- Chef::Log.debug "#{@new_resource} does not exist - nothing to do"
+ logger.trace "#{@new_resource} does not exist - nothing to do"
end
end
@@ -126,7 +123,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
state = current_state
if state == RUNNING
if @new_resource.stop_command
- Chef::Log.debug "#{@new_resource} stopping service using the given stop_command"
+ logger.trace "#{@new_resource} stopping service using the given stop_command"
shell_out!(@new_resource.stop_command)
else
spawn_command_thread do
@@ -136,22 +133,22 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
end
@new_resource.updated_by_last_action(true)
elsif state == STOPPED
- Chef::Log.debug "#{@new_resource} already stopped - nothing to do"
+ logger.trace "#{@new_resource} already stopped - nothing to do"
elsif state == STOP_PENDING
- Chef::Log.debug "#{@new_resource} already sent stop signal - waiting for stop"
+ logger.trace "#{@new_resource} already sent stop signal - waiting for stop"
wait_for_state(STOPPED)
else
raise Chef::Exceptions::Service, "Service #{@new_resource} can't be stopped from state [#{state}]"
end
else
- Chef::Log.debug "#{@new_resource} does not exist - nothing to do"
+ logger.trace "#{@new_resource} does not exist - nothing to do"
end
end
def restart_service
if Win32::Service.exists?(@new_resource.service_name)
if @new_resource.restart_command
- Chef::Log.debug "#{@new_resource} restarting service using the given restart_command"
+ logger.trace "#{@new_resource} restarting service using the given restart_command"
shell_out!(@new_resource.restart_command)
else
stop_service
@@ -159,7 +156,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
end
@new_resource.updated_by_last_action(true)
else
- Chef::Log.debug "#{@new_resource} does not exist - nothing to do"
+ logger.trace "#{@new_resource} does not exist - nothing to do"
end
end
@@ -167,7 +164,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
if Win32::Service.exists?(@new_resource.service_name)
set_startup_type(:automatic)
else
- Chef::Log.debug "#{@new_resource} does not exist - nothing to do"
+ logger.trace "#{@new_resource} does not exist - nothing to do"
end
end
@@ -175,85 +172,140 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
if Win32::Service.exists?(@new_resource.service_name)
set_startup_type(:disabled)
else
- Chef::Log.debug "#{@new_resource} does not exist - nothing to do"
+ logger.trace "#{@new_resource} does not exist - nothing to do"
+ end
+ end
+
+ action :create do
+ if Win32::Service.exists?(new_resource.service_name)
+ logger.trace "#{new_resource} already exists - nothing to do"
+ return
+ end
+
+ converge_by("create service #{new_resource.service_name}") do
+ Win32::Service.new(windows_service_config)
+ end
+
+ converge_delayed_start
+ end
+
+ action :delete do
+ unless Win32::Service.exists?(new_resource.service_name)
+ logger.trace "#{new_resource} does not exist - nothing to do"
+ return
+ end
+
+ converge_by("delete service #{new_resource.service_name}") do
+ Win32::Service.delete(new_resource.service_name)
end
end
- def action_enable
- if current_start_type != AUTO_START
+ action :configure do
+ unless Win32::Service.exists?(new_resource.service_name)
+ logger.warn "#{new_resource} does not exist. Maybe you need to prepend action :create"
+ return
+ end
+
+ converge_if_changed :service_type, :startup_type, :error_control,
+ :binary_path_name, :load_order_group, :dependencies,
+ :run_as_user, :display_name, :description do
+ Win32::Service.configure(windows_service_config(:configure))
+ end
+
+ converge_delayed_start
+ end
+
+ action :enable do
+ if current_startup_type != :automatic
converge_by("enable service #{@new_resource}") do
enable_service
- Chef::Log.info("#{@new_resource} enabled")
+ logger.info("#{@new_resource} enabled")
end
else
- Chef::Log.debug("#{@new_resource} already enabled - nothing to do")
+ logger.trace("#{@new_resource} already enabled - nothing to do")
end
load_new_resource_state
@new_resource.enabled(true)
end
- def action_disable
- if current_start_type != DISABLED
+ action :disable do
+ if current_startup_type != :disabled
converge_by("disable service #{@new_resource}") do
disable_service
- Chef::Log.info("#{@new_resource} disabled")
+ logger.info("#{@new_resource} disabled")
end
else
- Chef::Log.debug("#{@new_resource} already disabled - nothing to do")
+ logger.trace("#{@new_resource} already disabled - nothing to do")
end
load_new_resource_state
@new_resource.enabled(false)
end
- def action_configure_startup
- case @new_resource.startup_type
- when :automatic
- if current_start_type != AUTO_START
- converge_by("set service #{@new_resource} startup type to automatic") do
- set_startup_type(:automatic)
- end
- else
- Chef::Log.debug("#{@new_resource} startup_type already automatic - nothing to do")
- end
- when :manual
- if current_start_type != MANUAL
- converge_by("set service #{@new_resource} startup type to manual") do
- set_startup_type(:manual)
- end
- else
- Chef::Log.debug("#{@new_resource} startup_type already manual - nothing to do")
- end
- when :disabled
- if current_start_type != DISABLED
- converge_by("set service #{@new_resource} startup type to disabled") do
- set_startup_type(:disabled)
- end
- else
- Chef::Log.debug("#{@new_resource} startup_type already disabled - nothing to do")
+ action :configure_startup do
+ startup_type = @new_resource.startup_type
+ if current_startup_type != startup_type
+ converge_by("set service #{@new_resource} startup type to #{startup_type}") do
+ set_startup_type(startup_type)
end
+ else
+ logger.trace("#{@new_resource} startup_type already #{startup_type} - nothing to do")
end
+ converge_delayed_start
+
# Avoid changing enabled from true/false for now
@new_resource.enabled(nil)
end
private
+ def configure_service_run_as_properties
+ return unless new_resource.property_is_set?(:run_as_user)
+
+ new_config = {
+ service_name: new_resource.service_name,
+ service_start_name: new_resource.run_as_user,
+ password: new_resource.run_as_password,
+ }.reject { |k, v| v.nil? || v.length == 0 }
+
+ Win32::Service.configure(new_config)
+ logger.info "#{new_resource} configured."
+
+ grant_service_logon(new_resource.run_as_user) if new_resource.run_as_user != "localsystem"
+ end
+
+ #
+ # Queries the delayed auto-start setting of the auto-start service. If
+ # the service is not auto-start, this will return nil.
+ #
+ # @return [Boolean, nil]
+ #
+ def current_delayed_start
+ case Win32::Service.delayed_start(new_resource.service_name)
+ when 0
+ false
+ when 1
+ true
+ end
+ end
+
def grant_service_logon(username)
+ return if Chef::ReservedNames::Win32::Security.get_account_right(canonicalize_username(username)).include?(SERVICE_RIGHT)
+
begin
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}"
+ logger.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."
+ logger.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(%r{[/\\. ]+}, "_")
end
def canonicalize_username(username)
@@ -264,8 +316,9 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
Win32::Service.status(@new_resource.service_name).current_state
end
- def current_start_type
- Win32::Service.config_info(@new_resource.service_name).start_type
+ def current_startup_type
+ start_type = Win32::Service.config_info(@new_resource.service_name).start_type
+ start_type_to_sym(start_type)
end
# Helper method that waits for a status to change its state since state
@@ -274,40 +327,154 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
retries = 0
loop do
break if current_state == desired_state
- raise Timeout::Error if ( retries += 1 ) > resource_timeout
+ raise Timeout::Error if ( retries += 1 ) > @new_resource.timeout
+
sleep 1
end
end
- def resource_timeout
- @resource_timeout ||= @new_resource.timeout || TIMEOUT
- end
-
def spawn_command_thread
worker = Thread.new do
yield
end
- Timeout.timeout(resource_timeout) do
+ Timeout.timeout(@new_resource.timeout) do
worker.join
end
end
- # Takes Win32::Service start_types
- def set_startup_type(type)
- # Set-Service Startup Type => Win32::Service Constant
- allowed_types = { :automatic => Win32::Service::AUTO_START,
- :manual => Win32::Service::DEMAND_START,
- :disabled => Win32::Service::DISABLED }
- unless allowed_types.keys.include?(type)
+ # @param type [Symbol]
+ # @return [Integer]
+ # @raise [Chef::Exceptions::ConfigurationError] if the startup type is
+ # not supported.
+ # @see Chef::Resource::WindowsService::ALLOWED_START_TYPES
+ def startup_type_to_int(type)
+ Chef::Resource::WindowsService::ALLOWED_START_TYPES.fetch(type) do
raise Chef::Exceptions::ConfigurationError, "#{@new_resource.name}: Startup type '#{type}' is not supported"
end
+ end
- Chef::Log.debug "#{@new_resource.name} setting start_type to #{type}"
+ # Takes Win32::Service start_types
+ def set_startup_type(type)
+ startup_type = startup_type_to_int(type)
+
+ logger.trace "#{@new_resource.name} setting start_type to #{type}"
Win32::Service.configure(
- :service_name => @new_resource.service_name,
- :start_type => allowed_types[type]
+ service_name: @new_resource.service_name,
+ start_type: startup_type
)
@new_resource.updated_by_last_action(true)
end
+
+ def windows_service_config(action = :create)
+ config = {}
+
+ config[:service_name] = new_resource.service_name
+ config[:display_name] = new_resource.display_name if new_resource.display_name
+ config[:service_type] = new_resource.service_type if new_resource.service_type
+ config[:start_type] = startup_type_to_int(new_resource.startup_type) if new_resource.startup_type
+ config[:error_control] = new_resource.error_control if new_resource.error_control
+ config[:binary_path_name] = new_resource.binary_path_name if new_resource.binary_path_name
+ config[:load_order_group] = new_resource.load_order_group if new_resource.load_order_group
+ config[:dependencies] = new_resource.dependencies if new_resource.dependencies
+ config[:service_start_name] = new_resource.run_as_user unless new_resource.run_as_user.empty?
+ config[:password] = new_resource.run_as_password unless new_resource.run_as_user.empty? || new_resource.run_as_password.empty?
+ config[:description] = new_resource.description if new_resource.description
+
+ case action
+ when :create
+ config[:desired_access] = new_resource.desired_access if new_resource.desired_access
+ end
+
+ config
+ end
+
+ def converge_delayed_start
+ converge_if_changed :delayed_start do
+ config = {}
+ config[:service_name] = new_resource.service_name
+ config[:delayed_start] = new_resource.delayed_start ? 1 : 0
+
+ Win32::Service.configure(config)
+ end
+ end
+
+ # @return [Symbol]
+ def start_type_to_sym(start_type)
+ case start_type
+ when "auto start"
+ :automatic
+ when "boot start"
+ raise("Unsupported start type, #{start_type}. Submit bug request to fix.")
+ when "demand start"
+ :manual
+ when "disabled"
+ :disabled
+ when "system start"
+ raise("Unsupported start type, #{start_type}. Submit bug request to fix.")
+ else
+ raise("Unsupported start type, #{start_type}. Submit bug request to fix.")
+ end
+ end
+
+ def get_service_type(service_type)
+ case service_type
+ when "file system driver"
+ SERVICE_FILE_SYSTEM_DRIVER
+ when "kernel driver"
+ SERVICE_KERNEL_DRIVER
+ when "own process"
+ SERVICE_WIN32_OWN_PROCESS
+ when "share process"
+ SERVICE_WIN32_SHARE_PROCESS
+ when "recognizer driver"
+ SERVICE_RECOGNIZER_DRIVER
+ when "driver"
+ SERVICE_DRIVER
+ when "win32"
+ SERVICE_WIN32
+ when "all"
+ SERVICE_TYPE_ALL
+ when "own process, interactive"
+ SERVICE_INTERACTIVE_PROCESS | SERVICE_WIN32_OWN_PROCESS
+ when "share process, interactive"
+ SERVICE_INTERACTIVE_PROCESS | SERVICE_WIN32_SHARE_PROCESS
+ else
+ raise("Unsupported service type, #{service_type}. Submit bug request to fix.")
+ end
+ end
+
+ # @return [Integer]
+ def get_start_type(start_type)
+ case start_type
+ when "auto start"
+ SERVICE_AUTO_START
+ when "boot start"
+ SERVICE_BOOT_START
+ when "demand start"
+ SERVICE_DEMAND_START
+ when "disabled"
+ SERVICE_DISABLED
+ when "system start"
+ SERVICE_SYSTEM_START
+ else
+ raise("Unsupported start type, #{start_type}. Submit bug request to fix.")
+ end
+ end
+
+ def get_error_control(error_control)
+ case error_control
+ when "critical"
+ SERVICE_ERROR_CRITICAL
+ when "ignore"
+ SERVICE_ERROR_IGNORE
+ when "normal"
+ SERVICE_ERROR_NORMAL
+ when "severe"
+ SERVICE_ERROR_SEVERE
+ else
+ nil
+ end
+ end
+
end
diff --git a/lib/chef/provider/subversion.rb b/lib/chef/provider/subversion.rb
index ea32283bc9..18fc9d3a3c 100644
--- a/lib/chef/provider/subversion.rb
+++ b/lib/chef/provider/subversion.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@kallistec.com>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,13 +16,12 @@
# limitations under the License.
#
-#TODO subversion and git should both extend from a base SCM provider.
+# TODO subversion and git should both extend from a base SCM provider.
-require "chef/log"
-require "chef/provider"
-require "chef/mixin/command"
+require_relative "../log"
+require_relative "../provider"
require "chef-config/mixin/fuzzy_hostname_matcher"
-require "fileutils"
+require "fileutils" unless defined?(FileUtils)
class Chef
class Provider
@@ -30,21 +29,16 @@ class Chef
provides :subversion
- SVN_INFO_PATTERN = /^([\w\s]+): (.+)$/
+ SVN_INFO_PATTERN = /^([\w\s]+): (.+)$/.freeze
- include Chef::Mixin::Command
include ChefConfig::Mixin::FuzzyHostnameMatcher
- def whyrun_supported?
- true
- end
-
def load_current_resource
- @current_resource = Chef::Resource::Subversion.new(@new_resource.name)
+ @current_resource = Chef::Resource::Subversion.new(new_resource.name)
- unless [:export, :force_export].include?(Array(@new_resource.action).first)
+ unless %i{export force_export}.include?(Array(new_resource.action).first)
if current_revision = find_current_revision
- @current_resource.revision current_revision
+ current_resource.revision current_revision
end
end
end
@@ -53,47 +47,47 @@ class Chef
requirements.assert(:all_actions) do |a|
# Make sure the parent dir exists, or else fail.
# for why run, print a message explaining the potential error.
- parent_directory = ::File.dirname(@new_resource.destination)
+ parent_directory = ::File.dirname(new_resource.destination)
a.assertion { ::File.directory?(parent_directory) }
a.failure_message(Chef::Exceptions::MissingParentDirectory,
- "Cannot clone #{@new_resource} to #{@new_resource.destination}, the enclosing directory #{parent_directory} does not exist")
+ "Cannot clone #{new_resource} to #{new_resource.destination}, the enclosing directory #{parent_directory} does not exist")
a.whyrun("Directory #{parent_directory} does not exist, assuming it would have been created")
end
end
- def action_checkout
+ action :checkout do
if target_dir_non_existent_or_empty?
- converge_by("perform checkout of #{@new_resource.repository} into #{@new_resource.destination}") do
+ converge_by("perform checkout of #{new_resource.repository} into #{new_resource.destination}") do
shell_out!(checkout_command, run_options)
end
else
- Chef::Log.debug "#{@new_resource} checkout destination #{@new_resource.destination} already exists or is a non-empty directory - nothing to do"
+ logger.trace "#{new_resource} checkout destination #{new_resource.destination} already exists or is a non-empty directory - nothing to do"
end
end
- def action_export
+ action :export do
if target_dir_non_existent_or_empty?
action_force_export
else
- Chef::Log.debug "#{@new_resource} export destination #{@new_resource.destination} already exists or is a non-empty directory - nothing to do"
+ logger.trace "#{new_resource} export destination #{new_resource.destination} already exists or is a non-empty directory - nothing to do"
end
end
- def action_force_export
- converge_by("export #{@new_resource.repository} into #{@new_resource.destination}") do
+ action :force_export do
+ converge_by("export #{new_resource.repository} into #{new_resource.destination}") do
shell_out!(export_command, run_options)
end
end
- def action_sync
+ action :sync do
assert_target_directory_valid!
- if ::File.exist?(::File.join(@new_resource.destination, ".svn"))
+ if ::File.exist?(::File.join(new_resource.destination, ".svn"))
current_rev = find_current_revision
- Chef::Log.debug "#{@new_resource} current revision: #{current_rev} target revision: #{revision_int}"
+ logger.trace "#{new_resource} current revision: #{current_rev} target revision: #{revision_int}"
unless current_revision_matches_target_revision?
- converge_by("sync #{@new_resource.destination} from #{@new_resource.repository}") do
+ converge_by("sync #{new_resource.destination} from #{new_resource.repository}") do
shell_out!(sync_command, run_options)
- Chef::Log.info "#{@new_resource} updated to revision: #{revision_int}"
+ logger.info "#{new_resource} updated to revision: #{revision_int}"
end
end
else
@@ -102,24 +96,24 @@ class Chef
end
def sync_command
- c = scm :update, @new_resource.svn_arguments, verbose, authentication, proxy, "-r#{revision_int}", @new_resource.destination
- Chef::Log.debug "#{@new_resource} updated working copy #{@new_resource.destination} to revision #{@new_resource.revision}"
+ c = scm :update, new_resource.svn_arguments, verbose, authentication, proxy, "-r#{revision_int}", new_resource.destination
+ logger.trace "#{new_resource} updated working copy #{new_resource.destination} to revision #{new_resource.revision}"
c
end
def checkout_command
- c = scm :checkout, @new_resource.svn_arguments, verbose, authentication, proxy,
- "-r#{revision_int}", @new_resource.repository, @new_resource.destination
- Chef::Log.info "#{@new_resource} checked out #{@new_resource.repository} at revision #{@new_resource.revision} to #{@new_resource.destination}"
+ c = scm :checkout, new_resource.svn_arguments, verbose, authentication, proxy,
+ "-r#{revision_int}", new_resource.repository, new_resource.destination
+ logger.info "#{new_resource} checked out #{new_resource.repository} at revision #{new_resource.revision} to #{new_resource.destination}"
c
end
def export_command
args = ["--force"]
- args << @new_resource.svn_arguments << verbose << authentication << proxy <<
- "-r#{revision_int}" << @new_resource.repository << @new_resource.destination
+ args << new_resource.svn_arguments << verbose << authentication << proxy <<
+ "-r#{revision_int}" << new_resource.repository << new_resource.destination
c = scm :export, *args
- Chef::Log.info "#{@new_resource} exported #{@new_resource.repository} at revision #{@new_resource.revision} to #{@new_resource.destination}"
+ logger.info "#{new_resource} exported #{new_resource.repository} at revision #{new_resource.revision} to #{new_resource.destination}"
c
end
@@ -128,11 +122,11 @@ class Chef
# If the specified revision is an integer, trust it.
def revision_int
@revision_int ||= begin
- if @new_resource.revision =~ /^\d+$/
- @new_resource.revision
+ if /^\d+$/.match?(new_resource.revision)
+ new_resource.revision
else
- command = scm(:info, @new_resource.repository, @new_resource.svn_info_args, authentication, "-r#{@new_resource.revision}")
- svn_info = shell_out!(command, run_options(:cwd => cwd, :returns => [0, 1])).stdout
+ command = scm(:info, new_resource.repository, new_resource.svn_info_args, authentication, "-r#{new_resource.revision}")
+ svn_info = shell_out!(command, run_options(cwd: cwd, returns: [0, 1])).stdout
extract_revision_info(svn_info)
end
@@ -142,28 +136,35 @@ class Chef
alias :revision_slug :revision_int
def find_current_revision
- return nil unless ::File.exist?(::File.join(@new_resource.destination, ".svn"))
+ return nil unless ::File.exist?(::File.join(new_resource.destination, ".svn"))
+
command = scm(:info)
- svn_info = shell_out!(command, run_options(:cwd => cwd, :returns => [0, 1])).stdout
+ svn_info = shell_out!(command, run_options(cwd: cwd, returns: [0, 1])).stdout
extract_revision_info(svn_info)
end
def current_revision_matches_target_revision?
- (!@current_resource.revision.nil?) && (revision_int.strip.to_i == @current_resource.revision.strip.to_i)
+ (!current_resource.revision.nil?) && (revision_int.strip.to_i == current_resource.revision.strip.to_i)
end
def run_options(run_opts = {})
- run_opts[:user] = @new_resource.user if @new_resource.user
- run_opts[:group] = @new_resource.group if @new_resource.group
- run_opts[:timeout] = @new_resource.timeout if @new_resource.timeout
+ env = {}
+ if new_resource.user
+ run_opts[:user] = new_resource.user
+ env["HOME"] = get_homedir(new_resource.user)
+ end
+ run_opts[:group] = new_resource.group if new_resource.group
+ run_opts[:timeout] = new_resource.timeout if new_resource.timeout
+ env.merge!(new_resource.environment) if new_resource.environment
+ run_opts[:environment] = env unless env.empty?
run_opts
end
private
def cwd
- @new_resource.destination
+ new_resource.destination
end
def verbose
@@ -181,7 +182,8 @@ class Chef
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}"
+
+ logger.trace "#{new_resource} resolved revision #{new_resource.revision} to #{rev}"
rev
end
@@ -190,14 +192,15 @@ class Chef
# switch, since Capistrano will check for that prompt in the output
# and will respond appropriately.
def authentication
- return "" unless @new_resource.svn_username
- result = "--username #{@new_resource.svn_username} "
- result << "--password #{@new_resource.svn_password} "
+ return "" unless new_resource.svn_username
+
+ result = "--username #{new_resource.svn_username} "
+ result << "--password #{new_resource.svn_password} "
result
end
def proxy
- repo_uri = URI.parse(@new_resource.repository)
+ repo_uri = URI.parse(new_resource.repository)
proxy_uri = Chef::Config.proxy_uri(repo_uri.scheme, repo_uri.host, repo_uri.port)
return "" if proxy_uri.nil?
@@ -208,26 +211,40 @@ class Chef
def scm(*args)
binary = svn_binary
- binary = "\"#{binary}\"" if binary =~ /\s/
+ binary = "\"#{binary}\"" if /\s/.match?(binary)
[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")
+ new_resource.svn_binary ||
+ (ChefUtils.windows? ? "svn.exe" : "svn")
end
def assert_target_directory_valid!
- target_parent_directory = ::File.dirname(@new_resource.destination)
+ target_parent_directory = ::File.dirname(new_resource.destination)
unless ::File.directory?(target_parent_directory)
- msg = "Cannot clone #{@new_resource} to #{@new_resource.destination}, the enclosing directory #{target_parent_directory} does not exist"
+ msg = "Cannot clone #{new_resource} to #{new_resource.destination}, the enclosing directory #{target_parent_directory} does not exist"
raise Chef::Exceptions::MissingParentDirectory, msg
end
end
+
+ # Returns the home directory of the user
+ # @param [String] user must be a string.
+ # @return [String] the home directory of the user.
+ #
+ def get_homedir(user)
+ require "etc" unless defined?(Etc)
+ case user
+ when Integer
+ Etc.getpwuid(user).dir
+ else
+ Etc.getpwnam(user.to_s).dir
+ end
+ end
end
end
end
diff --git a/lib/chef/provider/support/yum_repo.erb b/lib/chef/provider/support/yum_repo.erb
index 7d9a2d09e2..7a55c1b7d2 100644
--- a/lib/chef/provider/support/yum_repo.erb
+++ b/lib/chef/provider/support/yum_repo.erb
@@ -4,8 +4,13 @@
[<%= @config.repositoryid %>]
name=<%= @config.description %>
<% if @config.baseurl %>
-baseurl=<%= @config.baseurl %>
-<% end %>
+baseurl=<%= case @config.baseurl
+ when Array
+ @config.baseurl.join("\n ")
+ else
+ @config.baseurl
+ end %>
+<% end -%>
<% if @config.cost %>
cost=<%= @config.cost %>
<% end %>
@@ -24,7 +29,9 @@ exclude=<%= @config.exclude %>
failovermethod=<%= @config.failovermethod %>
<% end %>
<% if @config.fastestmirror_enabled %>
-fastestmirror_enabled=<%= @config.fastestmirror_enabled %>
+fastestmirror_enabled=1
+<% else %>
+fastestmirror_enabled=0
<% end %>
<% if @config.gpgcheck %>
gpgcheck=1
@@ -54,6 +61,9 @@ keepalive=1
<% if @config.metadata_expire %>
metadata_expire=<%= @config.metadata_expire %>
<% end %>
+<% if @config.metalink %>
+metalink=<%= @config.metalink %>
+<% end %>
<% if @config.mirrorlist %>
mirrorlist=<%= @config.mirrorlist %>
<% end %>
@@ -105,6 +115,9 @@ sslclientkey=<%= @config.sslclientkey %>
<% unless @config.sslverify.nil? %>
sslverify=<%= ( @config.sslverify ) ? 'true' : 'false' %>
<% end %>
+<% if @config.throttle %>
+throttle=<%= @config.throttle %>
+<% end %>
<% if @config.timeout %>
timeout=<%= @config.timeout %>
<% end %>
diff --git a/lib/chef/provider/support/zypper_repo.erb b/lib/chef/provider/support/zypper_repo.erb
new file mode 100644
index 0000000000..6d508fa77f
--- /dev/null
+++ b/lib/chef/provider/support/zypper_repo.erb
@@ -0,0 +1,17 @@
+# This file was generated by Chef
+# Do NOT modify this file by hand.
+
+[<%= @config.repo_name %>]
+<% %w{ type enabled autorefresh gpgcheck gpgkey baseurl mirrorlist path priority keeppackages mode refresh_cache }.each do |prop| -%>
+<% next if @config.send(prop.to_sym).nil? -%>
+<%= prop %>=<%=
+ case @config.send(prop.to_sym)
+ when TrueClass
+ '1'
+ when FalseClass
+ '0'
+ else
+ @config.send(prop.to_sym)
+ end %>
+<% end -%>
+name=<%= @config.description || @config.repo_name %>
diff --git a/lib/chef/provider/systemd_unit.rb b/lib/chef/provider/systemd_unit.rb
index 4aa4cd0aa2..97043e48c7 100644
--- a/lib/chef/provider/systemd_unit.rb
+++ b/lib/chef/provider/systemd_unit.rb
@@ -1,6 +1,6 @@
#
# Author:: Nathan Williams (<nath.e.will@gmail.com>)
-# Copyright:: Copyright 2016, Nathan Williams
+# Copyright:: Copyright 2016-2018, Nathan Williams
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,29 +16,31 @@
# limitations under the License.
#
-require "chef/provider"
-require "chef/mixin/which"
-require "chef/mixin/shell_out"
-require "chef/resource/file"
+require_relative "../provider"
+require_relative "../mixin/which"
+require_relative "../resource/file"
+require_relative "../resource/file/verification/systemd_unit"
require "iniparse"
+require "shellwords" unless defined?(Shellwords)
class Chef
class Provider
class SystemdUnit < Chef::Provider
include Chef::Mixin::Which
- include Chef::Mixin::ShellOut
- provides :systemd_unit, os: "linux"
+ provides :systemd_unit
def load_current_resource
@current_resource = Chef::Resource::SystemdUnit.new(new_resource.name)
+ current_resource.unit_name(new_resource.unit_name)
current_resource.content(::File.read(unit_path)) if ::File.exist?(unit_path)
current_resource.user(new_resource.user)
current_resource.enabled(enabled?)
current_resource.active(active?)
current_resource.masked(masked?)
current_resource.static(static?)
+ current_resource.indirect(indirect?)
current_resource.triggers_reload(new_resource.triggers_reload)
current_resource
@@ -53,164 +55,207 @@ class Chef
end
end
- def action_create
+ action :create do
if current_resource.content != new_resource.to_ini
- converge_by("creating unit: #{new_resource.name}") do
+ converge_by("creating unit: #{new_resource.unit_name}") do
manage_unit_file(:create)
daemon_reload if new_resource.triggers_reload
end
end
end
- def action_delete
+ action :delete do
if ::File.exist?(unit_path)
- converge_by("deleting unit: #{new_resource.name}") do
+ converge_by("deleting unit: #{new_resource.unit_name}") do
manage_unit_file(:delete)
daemon_reload if new_resource.triggers_reload
end
end
end
- def action_enable
+ action :preset do
+ converge_by("restoring enable/disable preset configuration for unit: #{new_resource.unit_name}") do
+ systemctl_execute!(:preset, new_resource.unit_name)
+ end
+ end
+
+ action :revert do
+ converge_by("reverting to vendor version of unit: #{new_resource.unit_name}") do
+ systemctl_execute!(:revert, new_resource.unit_name)
+ end
+ end
+
+ action :enable do
if current_resource.static
- Chef::Log.debug("#{new_resource.name} is a static unit, enabling is a NOP.")
+ logger.trace("#{new_resource.unit_name} is a static unit, enabling is a NOP.")
+ end
+ if current_resource.indirect
+ logger.trace("#{new_resource.unit_name} is an indirect unit, enabling is a NOP.")
end
- unless current_resource.enabled || current_resource.static
- converge_by("enabling unit: #{new_resource.name}") do
- systemctl_execute!(:enable, new_resource.name)
+ unless current_resource.enabled || current_resource.static || current_resource.indirect
+ converge_by("enabling unit: #{new_resource.unit_name}") do
+ systemctl_execute!(:enable, new_resource.unit_name)
+ logger.info("#{new_resource} enabled")
end
end
end
- def action_disable
+ action :disable do
if current_resource.static
- Chef::Log.debug("#{new_resource.name} is a static unit, disabling is a NOP.")
+ logger.trace("#{new_resource.unit_name} is a static unit, disabling is a NOP.")
end
- if current_resource.enabled && !current_resource.static
- converge_by("disabling unit: #{new_resource.name}") do
- systemctl_execute!(:disable, new_resource.name)
+ if current_resource.indirect
+ logger.trace("#{new_resource.unit_name} is an indirect unit, enabling is a NOP.")
+ end
+
+ if current_resource.enabled && !current_resource.static && !current_resource.indirect
+ converge_by("disabling unit: #{new_resource.unit_name}") do
+ systemctl_execute!(:disable, new_resource.unit_name)
+ logger.info("#{new_resource} disabled")
end
end
end
- def action_mask
+ action :reenable do
+ converge_by("reenabling unit: #{new_resource.unit_name}") do
+ systemctl_execute!(:reenable, new_resource.unit_name)
+ logger.info("#{new_resource} reenabled")
+ end
+ end
+
+ action :mask do
unless current_resource.masked
- converge_by("masking unit: #{new_resource.name}") do
- systemctl_execute!(:mask, new_resource.name)
+ converge_by("masking unit: #{new_resource.unit_name}") do
+ systemctl_execute!(:mask, new_resource.unit_name)
+ logger.info("#{new_resource} masked")
end
end
end
- def action_unmask
+ action :unmask do
if current_resource.masked
- converge_by("unmasking unit: #{new_resource.name}") do
- systemctl_execute!(:unmask, new_resource.name)
+ converge_by("unmasking unit: #{new_resource.unit_name}") do
+ systemctl_execute!(:unmask, new_resource.unit_name)
+ logger.info("#{new_resource} unmasked")
end
end
end
- def action_start
+ action :start do
unless current_resource.active
- converge_by("starting unit: #{new_resource.name}") do
- systemctl_execute!(:start, new_resource.name)
+ converge_by("starting unit: #{new_resource.unit_name}") do
+ systemctl_execute!(:start, new_resource.unit_name, default_env: false)
+ logger.info("#{new_resource} started")
end
end
end
- def action_stop
+ action :stop do
if current_resource.active
- converge_by("stopping unit: #{new_resource.name}") do
- systemctl_execute!(:stop, new_resource.name)
+ converge_by("stopping unit: #{new_resource.unit_name}") do
+ systemctl_execute!(:stop, new_resource.unit_name, default_env: false)
+ logger.info("#{new_resource} stopped")
end
end
end
- def action_restart
- converge_by("restarting unit: #{new_resource.name}") do
- systemctl_execute!(:restart, new_resource.name)
+ action :restart do
+ converge_by("restarting unit: #{new_resource.unit_name}") do
+ systemctl_execute!(:restart, new_resource.unit_name, default_env: false)
+ logger.info("#{new_resource} restarted")
end
end
- def action_reload
+ action :reload do
if current_resource.active
- converge_by("reloading unit: #{new_resource.name}") do
- systemctl_execute!(:reload, new_resource.name)
+ converge_by("reloading unit: #{new_resource.unit_name}") do
+ systemctl_execute!(:reload, new_resource.unit_name, default_env: false)
+ logger.info("#{new_resource} reloaded")
end
else
- Chef::Log.debug("#{new_resource.name} is not active, skipping reload.")
+ logger.trace("#{new_resource.unit_name} is not active, skipping reload.")
end
end
- def action_try_restart
- converge_by("try-restarting unit: #{new_resource.name}") do
- systemctl_execute!("try-restart", new_resource.name)
+ action :try_restart do
+ converge_by("try-restarting unit: #{new_resource.unit_name}") do
+ systemctl_execute!("try-restart", new_resource.unit_name, default_env: false)
+ logger.info("#{new_resource} try-restarted")
end
end
- def action_reload_or_restart
- converge_by("reload-or-restarting unit: #{new_resource.name}") do
- systemctl_execute!("reload-or-restart", new_resource.name)
+ action :reload_or_restart do
+ converge_by("reload-or-restarting unit: #{new_resource.unit_name}") do
+ systemctl_execute!("reload-or-restart", new_resource.unit_name, default_env: false)
+ logger.info("#{new_resource} reload-or-restarted")
end
end
- def action_reload_or_try_restart
- converge_by("reload-or-try-restarting unit: #{new_resource.name}") do
- systemctl_execute!("reload-or-try-restart", new_resource.name)
+ action :reload_or_try_restart do
+ converge_by("reload-or-try-restarting unit: #{new_resource.unit_name}") do
+ systemctl_execute!("reload-or-try-restart", new_resource.unit_name, default_env: false)
+ logger.info("#{new_resource} reload-or-try-restarted")
end
end
def active?
- systemctl_execute("is-active", new_resource.name).exitstatus == 0
+ systemctl_execute("is-active", new_resource.unit_name).exitstatus == 0
end
def enabled?
- systemctl_execute("is-enabled", new_resource.name).exitstatus == 0
+ systemctl_execute("is-enabled", new_resource.unit_name).exitstatus == 0
end
def masked?
- systemctl_execute(:status, new_resource.name).stdout.include?("masked")
+ systemctl_execute("status", new_resource.unit_name).stdout.include?("masked")
end
def static?
- systemctl_execute("is-enabled", new_resource.name).stdout.include?("static")
+ systemctl_execute("is-enabled", new_resource.unit_name).stdout.include?("static")
+ end
+
+ def indirect?
+ systemctl_execute("is-enabled", new_resource.unit_name).stdout.include?("indirect")
end
private
def unit_path
if new_resource.user
- "/etc/systemd/user/#{new_resource.name}"
+ "/etc/systemd/user/#{new_resource.unit_name}"
else
- "/etc/systemd/system/#{new_resource.name}"
+ "/etc/systemd/system/#{new_resource.unit_name}"
end
end
- def manage_unit_file(action = :nothing)
- Chef::Resource::File.new(unit_path, run_context).tap do |f|
- f.owner "root"
- f.group "root"
- f.mode "0644"
- f.content new_resource.to_ini
- f.verify systemd_analyze_cmd if systemd_analyze_path
- end.run_action(action)
+ def manage_unit_file(the_action = :nothing)
+ file unit_path do
+ owner "root"
+ group "root"
+ mode "0644"
+ sensitive new_resource.sensitive
+ content new_resource.to_ini
+ verify :systemd_unit if new_resource.verify
+ action the_action
+ end
end
def daemon_reload
- shell_out_with_systems_locale!("#{systemctl_path} daemon-reload")
+ shell_out!(systemctl_cmd, "daemon-reload", **systemctl_opts, default_env: false)
end
- def systemctl_execute!(action, unit)
- shell_out_with_systems_locale!("#{systemctl_cmd} #{action} #{unit}", systemctl_opts)
+ def systemctl_execute!(action, unit, **options)
+ shell_out!(systemctl_cmd, action, unit, **systemctl_opts.merge(options))
end
- def systemctl_execute(action, unit)
- shell_out("#{systemctl_cmd} #{action} #{unit}", systemctl_opts)
+ def systemctl_execute(action, unit, **options)
+ shell_out(systemctl_cmd, action, unit, **systemctl_opts.merge(options))
end
def systemctl_cmd
- @systemctl_cmd ||= "#{systemctl_path} #{systemctl_args}"
+ @systemctl_cmd ||= [ systemctl_path, systemctl_args ]
end
def systemctl_path
@@ -224,24 +269,17 @@ class Chef
def systemctl_opts
@systemctl_opts ||=
if new_resource.user
+ uid = Etc.getpwnam(new_resource.user).uid
{
- :user => new_resource.user,
- :environment => {
- "DBUS_SESSION_BUS_ADDRESS" => "unix:path=/run/user/#{node['etc']['passwd'][new_resource.user]['uid']}/bus",
+ user: new_resource.user,
+ environment: {
+ "DBUS_SESSION_BUS_ADDRESS" => "unix:path=/run/user/#{uid}/bus",
},
}
else
{}
end
end
-
- def systemd_analyze_cmd
- @systemd_analyze_cmd ||= "#{systemd_analyze_path} verify %{path}"
- end
-
- def systemd_analyze_path
- @systemd_analyze_path ||= which("systemd-analyze")
- end
end
end
end
diff --git a/lib/chef/provider/template.rb b/lib/chef/provider/template.rb
index 3c46a6eb7d..6662821aae 100644
--- a/lib/chef/provider/template.rb
+++ b/lib/chef/provider/template.rb
@@ -1,7 +1,7 @@
#--
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,27 +17,21 @@
# limitations under the License.
#
-require "chef/provider/template_finder"
-require "chef/provider/file"
-require "chef/deprecation/provider/template"
-require "chef/deprecation/warnings"
+require_relative "template_finder"
+require_relative "file"
class Chef
class Provider
class Template < Chef::Provider::File
provides :template
- extend Chef::Deprecation::Warnings
- include Chef::Deprecation::Provider::Template
- add_deprecation_warnings_for(Chef::Deprecation::Provider::Template.instance_methods)
-
def initialize(new_resource, run_context)
@content_class = Chef::Provider::Template::Content
super
end
def load_current_resource
- @current_resource = Chef::Resource::Template.new(@new_resource.name)
+ @current_resource = Chef::Resource::Template.new(new_resource.name)
super
end
@@ -55,8 +49,9 @@ class Chef
private
def managing_content?
- return true if @new_resource.checksum
- return true if !@new_resource.source.nil? && @action != :create_if_missing
+ return true if new_resource.checksum
+ return true if !new_resource.source.nil? && @action != :create_if_missing
+
false
end
diff --git a/lib/chef/provider/template/content.rb b/lib/chef/provider/template/content.rb
index acf200621d..a0977b5523 100644
--- a/lib/chef/provider/template/content.rb
+++ b/lib/chef/provider/template/content.rb
@@ -1,6 +1,7 @@
+# rubocop: disable Performance/InefficientHashSearch
#
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,8 +17,8 @@
# limitations under the License.
#
-require "chef/mixin/template"
-require "chef/file_content_management/content_base"
+require_relative "../../mixin/template"
+require_relative "../../file_content_management/content_base"
class Chef
class Provider
@@ -29,14 +30,37 @@ 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)
+ # Deal with any DelayedEvaluator values in the template variables.
+ visitor = lambda do |obj|
+ case obj
+ when Hash
+ # If this is an Attribute object, we need to change class otherwise
+ # we get the immutable behavior. This could probably be fixed by
+ # using Hash#transform_values once we only support Ruby 2.4.
+ obj_class = obj.is_a?(Chef::Node::ImmutableMash) ? Mash : obj.class
+ # Avoid mutating hashes in the resource in case we're changing anything.
+ obj.each_with_object(obj_class.new) do |(key, value), memo|
+ memo[key] = visitor.call(value)
+ end
+ when Array
+ # Avoid mutating arrays in the resource in case we're changing anything.
+ obj.map { |value| visitor.call(value) }
+ when DelayedEvaluator
+ new_resource.instance_eval(&obj)
+ else
+ obj
+ end
+ end
+ variables = visitor.call(new_resource.variables)
+
+ context = TemplateContext.new(variables)
context[:node] = run_context.node
context[:template_finder] = template_finder
diff --git a/lib/chef/provider/template_finder.rb b/lib/chef/provider/template_finder.rb
index 1e8b925071..fa120a1624 100644
--- a/lib/chef/provider/template_finder.rb
+++ b/lib/chef/provider/template_finder.rb
@@ -1,6 +1,6 @@
#--
# Author:: Andrea Campi (<andrea.campi@zephirworks.com>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -43,19 +43,11 @@ class Chef
protected
def template_source_name(name, options)
- if options[:source]
- options[:source]
- else
- name
- end
+ options[:source] || name
end
def find_cookbook_name(options)
- if options[:cookbook]
- options[:cookbook]
- else
- @cookbook_name
- end
+ options[:cookbook] || @cookbook_name
end
end
end
diff --git a/lib/chef/provider/user.rb b/lib/chef/provider/user.rb
index 2bc4cc10bc..803d87d820 100644
--- a/lib/chef/provider/user.rb
+++ b/lib/chef/provider/user.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,16 +16,15 @@
# limitations under the License.
#
-require "chef/provider"
-require "chef/mixin/command"
-require "etc"
+require_relative "../provider"
+require "etc" unless defined?(Etc)
class Chef
class Provider
class User < Chef::Provider
- include Chef::Mixin::Command
attr_accessor :user_exists, :locked
+ attr_accessor :change_desc
def initialize(new_resource, run_context)
super
@@ -36,74 +35,70 @@ class Chef
end
def convert_group_name
- if @new_resource.gid.is_a? String
- @new_resource.gid(Etc.getgrnam(@new_resource.gid).gid)
+ if new_resource.gid.is_a?(String) && new_resource.gid.to_i == 0
+ new_resource.gid(Etc.getgrnam(new_resource.gid).gid)
end
- rescue ArgumentError => e
+ rescue ArgumentError
@group_name_resolved = false
end
- def whyrun_supported?
- true
- end
-
def load_current_resource
- @current_resource = Chef::Resource::User.new(@new_resource.name)
- @current_resource.username(@new_resource.username)
+ @current_resource = Chef::Resource::User.new(new_resource.name)
+ current_resource.username(new_resource.username)
begin
- user_info = Etc.getpwnam(@new_resource.username)
- rescue ArgumentError => e
+ user_info = Etc.getpwnam(new_resource.username)
+ rescue ArgumentError
@user_exists = false
- Chef::Log.debug("#{@new_resource} user does not exist")
+ logger.trace("#{new_resource} user does not exist")
user_info = nil
end
if user_info
- @current_resource.uid(user_info.uid)
- @current_resource.gid(user_info.gid)
- @current_resource.home(user_info.dir)
- @current_resource.shell(user_info.shell)
- @current_resource.password(user_info.passwd)
-
- if @new_resource.comment
- user_info.gecos.force_encoding(@new_resource.comment.encoding)
+ current_resource.uid(user_info.uid)
+ current_resource.gid(user_info.gid)
+ current_resource.home(user_info.dir)
+ current_resource.shell(user_info.shell)
+ current_resource.password(user_info.passwd)
+
+ if new_resource.comment
+ user_info.gecos.force_encoding(new_resource.comment.encoding)
end
- @current_resource.comment(user_info.gecos)
+ 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"
rescue LoadError
@shadow_lib_ok = false
else
- shadow_info = Shadow::Passwd.getspnam(@new_resource.username)
- @current_resource.password(shadow_info.sp_pwdp)
+ shadow_info = Shadow::Passwd.getspnam(new_resource.username)
+ current_resource.password(shadow_info.sp_pwdp)
end
end
- convert_group_name if @new_resource.gid
+ convert_group_name if new_resource.gid
end
- @current_resource
+ current_resource
end
def define_resource_requirements
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."
+ a.failure_message Chef::Exceptions::User, "Couldn't lookup integer GID for group name #{new_resource.gid}"
+ a.whyrun "group name #{new_resource.gid} does not exist. This will cause group assignment to fail. Assuming this group will have been created previously."
end
requirements.assert(:all_actions) do |a|
a.assertion { @shadow_lib_ok }
a.failure_message Chef::Exceptions::MissingLibrary, "You must have ruby-shadow installed for password support!"
- a.whyrun "ruby-shadow is not installed. Attempts to set user password will cause failure. Assuming that this gem will have been previously installed." +
+ a.whyrun "ruby-shadow is not installed. Attempts to set user password will cause failure. Assuming that this gem will have been previously installed." \
"Note that user update converge may report false-positive on the basis of mismatched password. "
end
requirements.assert(:modify, :lock, :unlock) do |a|
a.assertion { @user_exists }
- a.failure_message(Chef::Exceptions::User, "Cannot modify user #{@new_resource.username} - does not exist!")
- a.whyrun("Assuming user #{@new_resource.username} would have been created")
+ a.failure_message(Chef::Exceptions::User, "Cannot modify user #{new_resource.username} - does not exist!")
+ a.whyrun("Assuming user #{new_resource.username} would have been created")
end
end
@@ -113,77 +108,82 @@ class Chef
# <true>:: If a change is required
# <false>:: If the users are identical
def compare_user
- changed = [ :comment, :home, :shell, :password ].select do |user_attrib|
- !@new_resource.send(user_attrib).nil? && @new_resource.send(user_attrib) != @current_resource.send(user_attrib)
+ @change_desc = []
+ if !new_resource.home.nil? && Pathname.new(new_resource.home).cleanpath != Pathname.new(current_resource.home).cleanpath
+ @change_desc << "change homedir from #{current_resource.home} to #{new_resource.home}"
end
- changed += [ :uid, :gid ].select do |user_attrib|
- !@new_resource.send(user_attrib).nil? && @new_resource.send(user_attrib).to_s != @current_resource.send(user_attrib).to_s
+ %i{comment shell password uid gid}.each do |user_attrib|
+ new_val = new_resource.send(user_attrib)
+ cur_val = current_resource.send(user_attrib)
+ if !new_val.nil? && new_val.to_s != cur_val.to_s
+ @change_desc << "change #{user_attrib} from #{cur_val} to #{new_val}"
+ end
end
- changed.any?
+ !@change_desc.empty?
end
- def action_create
+ action :create do
if !@user_exists
- converge_by("create user #{@new_resource.username}") do
+ converge_by("create user #{new_resource.username}") do
create_user
- Chef::Log.info("#{@new_resource} created")
+ logger.info("#{new_resource} created")
end
elsif compare_user
- converge_by("alter user #{@new_resource.username}") do
+ converge_by(["alter user #{new_resource.username}"] + change_desc) do
manage_user
- Chef::Log.info("#{@new_resource} altered")
+ logger.info("#{new_resource} altered, #{change_desc.join(", ")}")
end
end
end
- def action_remove
- if @user_exists
- converge_by("remove user #{@new_resource.username}") do
- remove_user
- Chef::Log.info("#{@new_resource} removed")
- end
+ action :remove do
+ return unless @user_exists
+
+ converge_by("remove user #{new_resource.username}") do
+ remove_user
+ logger.info("#{new_resource} removed")
end
end
- def action_manage
- if @user_exists && compare_user
- converge_by("manage user #{@new_resource.username}") do
- manage_user
- Chef::Log.info("#{@new_resource} managed")
- end
+ action :manage do
+ return unless @user_exists && compare_user
+
+ converge_by(["manage user #{new_resource.username}"] + change_desc) do
+ manage_user
+ logger.info("#{new_resource} managed: #{change_desc.join(", ")}")
end
end
- def action_modify
- if compare_user
- converge_by("modify user #{@new_resource.username}") do
- manage_user
- Chef::Log.info("#{@new_resource} modified")
- end
+ action :modify do
+ return unless compare_user
+
+ converge_by(["modify user #{new_resource.username}"] + change_desc) do
+ manage_user
+ logger.info("#{new_resource} modified: #{change_desc.join(", ")}")
end
end
- def action_lock
- if check_lock() == false
- converge_by("lock the user #{@new_resource.username}") do
+ action :lock do
+ if check_lock == false
+ converge_by("lock the user #{new_resource.username}") do
lock_user
- Chef::Log.info("#{@new_resource} locked")
+ logger.info("#{new_resource} locked")
end
else
- Chef::Log.debug("#{@new_resource} already locked - nothing to do")
+ logger.trace("#{new_resource} already locked - nothing to do")
end
end
- def action_unlock
- if check_lock() == true
- converge_by("unlock user #{@new_resource.username}") do
+ action :unlock do
+ if check_lock == true
+ converge_by("unlock user #{new_resource.username}") do
unlock_user
- Chef::Log.info("#{@new_resource} unlocked")
+ logger.info("#{new_resource} unlocked")
end
else
- Chef::Log.debug("#{@new_resource} already unlocked - nothing to do")
+ logger.trace("#{new_resource} already unlocked - nothing to do")
end
end
@@ -210,6 +210,24 @@ class Chef
def check_lock
raise NotImplementedError
end
+
+ private
+
+ #
+ # helpers for subclasses
+ #
+
+ def should_set?(sym)
+ current_resource.send(sym).to_s != new_resource.send(sym).to_s && new_resource.send(sym)
+ end
+
+ def updating_home?
+ return false if new_resource.home.nil?
+ return true if current_resource.home.nil?
+
+ # Pathname#cleanpath matches more edge conditions than File.expand_path()
+ new_resource.home && Pathname.new(current_resource.home).cleanpath != Pathname.new(new_resource.home).cleanpath
+ end
end
end
end
diff --git a/lib/chef/provider/user/aix.rb b/lib/chef/provider/user/aix.rb
index 8ac229ae4d..740f9943d3 100644
--- a/lib/chef/provider/user/aix.rb
+++ b/lib/chef/provider/user/aix.rb
@@ -1,5 +1,5 @@
#
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,83 +14,117 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require "chef/provider/user/useradd"
+require_relative "../user"
class Chef
class Provider
class User
- class Aix < Chef::Provider::User::Useradd
+ class Aix < Chef::Provider::User
provides :user, os: "aix"
provides :aix_user
- UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:shell, "-s"], [:uid, "-u"]]
-
def create_user
- super
+ shell_out!("useradd", universal_options, useradd_options, new_resource.username)
add_password
end
def manage_user
add_password
manage_home
- super
+ return if universal_options.empty? && usermod_options.empty?
+
+ shell_out!("usermod", universal_options, usermod_options, new_resource.username)
+ end
+
+ def remove_user
+ shell_out!("userdel", userdel_options, new_resource.username)
end
- # Aix does not support -r like other unix, sytem account is created by adding to 'system' group
+ # Aix does not support -r like other unix, system account is created by adding to 'system' group
def useradd_options
opts = []
opts << "-g" << "system" if new_resource.system
+ if updating_home?
+ if new_resource.manage_home
+ logger.trace("#{new_resource} managing the users home directory")
+ opts << "-m"
+ else
+ logger.trace("#{new_resource} setting home to #{new_resource.home}")
+ end
+ end
opts
end
+ def userdel_options
+ opts = []
+ opts << "-r" if new_resource.manage_home
+ opts << "-f" if new_resource.force
+ opts
+ end
+
+ def usermod_options
+ []
+ end
+
def check_lock
- lock_info = shell_out!("lsuser -a account_locked #{new_resource.username}")
- if whyrun_mode? && passwd_s.stdout.empty? && lock_info.stderr.match(/does not exist/)
+ lock_info = shell_out!("lsuser", "-a", "account_locked", new_resource.username)
+ if whyrun_mode? && passwd_s.stdout.empty? && lock_info.stderr.include?("does not exist")
# if we're in whyrun mode and the user is not yet created we assume it would be
return false
end
- raise Chef::Exceptions::User, "Cannot determine if #{@new_resource} is locked!" if lock_info.stdout.empty?
+ raise Chef::Exceptions::User, "Cannot determine if #{new_resource} is locked!" if lock_info.stdout.empty?
status = /\S+\s+account_locked=(\S+)/.match(lock_info.stdout)
- if status && status[1] == "true"
- @locked = true
- else
- @locked = false
- end
+ @locked =
+ if status && status[1] == "true"
+ true
+ else
+ false
+ end
@locked
end
def lock_user
- shell_out!("chuser account_locked=true #{new_resource.username}")
+ shell_out!("chuser", "account_locked=true", new_resource.username)
end
def unlock_user
- shell_out!("chuser account_locked=false #{new_resource.username}")
+ shell_out!("chuser", "account_locked=false", new_resource.username)
+ end
+
+ def universal_options
+ opts = []
+ opts << "-c" << new_resource.comment if should_set?(:comment)
+ opts << "-g" << new_resource.gid if should_set?(:gid)
+ opts << "-s" << new_resource.shell if should_set?(:shell)
+ opts << "-u" << new_resource.uid if should_set?(:uid)
+ opts << "-d" << new_resource.home if updating_home?
+ opts << "-o" if new_resource.non_unique
+ opts
end
private
def add_password
- if @current_resource.password != @new_resource.password && @new_resource.password
- Chef::Log.debug("#{@new_resource.username} setting password to #{@new_resource.password}")
- command = "echo '#{@new_resource.username}:#{@new_resource.password}' | chpasswd -e"
- shell_out!(command)
- end
+ return unless current_resource.password != new_resource.password && new_resource.password
+
+ logger.trace("#{new_resource.username} setting password to #{new_resource.password}")
+ command = "echo '#{new_resource.username}:#{new_resource.password}' | chpasswd -c -e"
+ shell_out!(command)
end
# Aix specific handling to update users home directory.
def manage_home
+ return unless updating_home? && new_resource.manage_home
+
# -m option does not work on aix, so move dir.
- if updating_home? && managing_home_dir?
- universal_options.delete("-m")
- if ::File.directory?(@current_resource.home)
- Chef::Log.debug("Changing users home directory from #{@current_resource.home} to #{new_resource.home}")
- shell_out!("mv #{@current_resource.home} #{new_resource.home}")
- else
- Chef::Log.debug("Creating users home directory #{new_resource.home}")
- shell_out!("mkdir -p #{new_resource.home}")
- end
+ if ::File.directory?(current_resource.home)
+ logger.trace("Changing users home directory from #{current_resource.home} to #{new_resource.home}")
+ FileUtils.mv current_resource.home, new_resource.home
+ else
+ logger.trace("Creating users home directory #{new_resource.home}")
+ FileUtils.mkdir_p new_resource.home
end
end
diff --git a/lib/chef/provider/user/dscl.rb b/lib/chef/provider/user/dscl.rb
index 821fa8e8a7..cd10403e4a 100644
--- a/lib/chef/provider/user/dscl.rb
+++ b/lib/chef/provider/user/dscl.rb
@@ -1,6 +1,6 @@
#
# Author:: Dreamcat4 (<dreamcat4@gmail.com>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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 "mixlib/shellout"
-require "chef/provider/user"
-require "openssl"
-require "plist"
-require "chef/util/path_helper"
+require_relative "../../mixin/shell_out"
+require_relative "../user"
+require_relative "../../resource/user/dscl_user"
+autoload :OpenSSL, "openssl"
+autoload :Plist, "plist"
+require_relative "../../util/path_helper"
class Chef
class Provider
class User
#
# The most tricky bit of this provider is the way it deals with user passwords.
- # Mac OS X has different password shadow calculations based on the version.
+ # macOS has different password shadow calculations based on the version.
# < 10.7 => password shadow calculation format SALTED-SHA1
# => stored in: /var/db/shadow/hash/#{guid}
# => shadow binary length 68 bytes
@@ -41,7 +42,7 @@ class Chef
# => shadow binary length 128 bytes
# => Salt / Iterations are stored separately in the same file
#
- # This provider only supports Mac OSX versions 10.7 and above
+ # This provider only supports macOS versions 10.7 to 10.13
class Dscl < Chef::Provider::User
attr_accessor :user_info
@@ -49,29 +50,29 @@ class Chef
attr_accessor :password_shadow_conversion_algorithm
provides :dscl_user
- provides :user, os: "darwin"
+ provides :user, os: "darwin", platform_version: "<= 10.13"
+
+ # Just-in-case a recipe calls the user dscl provider without specifying
+ # a gid property. Avoids chown issues in move_home when the manage_home
+ # property is in use. #5393
+ STAFF_GROUP_ID = 20
def define_resource_requirements
super
requirements.assert(:all_actions) do |a|
- a.assertion { mac_osx_version_less_than_10_7? == false }
- a.failure_message(Chef::Exceptions::User, "Chef::Provider::User::Dscl only supports Mac OS X versions 10.7 and above.")
- end
-
- requirements.assert(:all_actions) do |a|
- a.assertion { ::File.exists?("/usr/bin/dscl") }
+ a.assertion { ::File.exist?("/usr/bin/dscl") }
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.assertion { ::File.exist?("/usr/bin/plutil") }
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
# SALTED-SHA512 password shadow hashes are not supported on 10.8 and above.
!salted_sha512?(new_resource.password)
else
@@ -85,7 +86,7 @@ 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 && 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?
@@ -96,24 +97,10 @@ in 'password', with the associated 'salt' and 'iterations'.")
a.failure_message(Chef::Exceptions::User, "SALTED-SHA512-PBKDF2 shadow hash is given without associated \
'salt' and 'iterations'. Please specify 'salt' and 'iterations' in order to set the user password using shadow hash.")
end
-
- requirements.assert(:create, :modify, :manage) do |a|
- a.assertion do
- 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)
- else
- true
- end
- end
- a.failure_message(Chef::Exceptions::User, "SALTED-SHA512-PBKDF2 shadow hashes are not supported on \
-Mac OS X version 10.7. Please specify a SALTED-SHA512 shadow hash in 'password' attribute to set the \
-user password using shadow hash.")
- end
end
def load_current_resource
- @current_resource = Chef::Resource::User.new(new_resource.username)
+ @current_resource = Chef::Resource::User::DsclUser.new(new_resource.username)
current_resource.username(new_resource.username)
@user_info = read_user_info
@@ -131,13 +118,9 @@ user password using 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)
- shadow_hash = Plist.parse_xml(shadow_hash_xml)
+ shadow_hash = ::Plist.parse_xml(shadow_hash_xml)
- 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)
- elsif shadow_hash["SALTED-SHA512-PBKDF2"]
+ if shadow_hash["SALTED-SHA512-PBKDF2"] # 10.7+ contains this, but we retain the check in case it goes away in the future
@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)
@@ -145,14 +128,14 @@ user password using shadow hash.")
# Convert the salt from Base64 encoding to hex before consuming them
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(' ')}")
+ raise(Chef::Exceptions::User, "Unknown shadow_hash format: #{shadow_hash.keys.join(" ")}")
end
end
convert_group_name if new_resource.gid
else
@user_exists = false
- Chef::Log.debug("#{new_resource} user does not exist")
+ logger.trace("#{new_resource} user does not exist")
end
current_resource
@@ -194,7 +177,7 @@ 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
#
@@ -203,13 +186,13 @@ user password using shadow hash.")
#
def dscl_create_comment
comment = new_resource.comment || new_resource.username
- run_dscl("create /Users/#{new_resource.username} RealName '#{comment}'")
+ run_dscl("create", "/Users/#{new_resource.username}", "RealName", comment)
end
#
# Sets the user id for the user using dscl.
# If a `uid` is not specified, it finds the next available one starting
- # from 200 if `system` is set, 500 otherwise.
+ # from 200 if `system` is set, 501 otherwise.
#
def dscl_set_uid
# XXX: mutates the new resource
@@ -219,27 +202,27 @@ user password using shadow hash.")
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
#
# Find the next available uid on the system. starting with 200 if `system` is set,
- # 500 otherwise.
+ # 501 otherwise.
#
def get_free_uid(search_limit = 1000)
uid = nil
- base_uid = new_resource.system ? 200 : 500
+ base_uid = new_resource.system ? 200 : 501
next_uid_guess = base_uid
- users_uids = run_dscl("list /Users uid")
+ users_uids = run_dscl("list", "/Users", "uid")
while next_uid_guess < search_limit + base_uid
- if users_uids =~ Regexp.new("#{Regexp.escape(next_uid_guess.to_s)}\n")
+ if users_uids&.match?(Regexp.new("#{Regexp.escape(next_uid_guess.to_s)}\n"))
next_uid_guess += 1
else
uid = next_uid_guess
break
end
end
- return uid || raise("uid not found. Exhausted. Searched #{search_limit} times")
+ uid || raise("uid not found. Exhausted. Searched #{search_limit} times")
end
#
@@ -247,39 +230,40 @@ user password using shadow hash.")
#
def uid_used?(uid)
return false unless uid
- users_uids = run_dscl("list /Users uid").split("\n")
- uid_map = users_uids.inject({}) do |tmap, tuid|
+
+ users_uids = run_dscl("list", "/Users", "uid").split("\n")
+ uid_map = users_uids.each_with_object({}) do |tuid, tmap|
x = tuid.split
tmap[x[1]] = x[0]
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
return true
end
end
- return false
+ false
end
#
# Sets the group id for the user using dscl. Fails if a group doesn't
# exist on the system with given group id. If `gid` is not specified, it
- # sets a default Mac user group "staff", with id 20.
+ # sets a default Mac user group "staff", with id 20 using the CONSTANT
#
def dscl_set_gid
if new_resource.gid.nil?
# XXX: mutates the new resource
- new_resource.gid(20)
+ new_resource.gid(STAFF_GROUP_ID)
elsif !new_resource.gid.to_s.match(/^\d+$/)
begin
- 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}")
+ possible_gid = run_dscl("read", "/Groups/#{new_resource.gid}", "PrimaryGroupID").split(" ").last
+ rescue Chef::Exceptions::DsclCommandFailed
+ raise Chef::Exceptions::GroupIDNotFound, "Group not found for #{new_resource.gid} when creating user #{new_resource.username}"
end
# 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
#
@@ -288,11 +272,11 @@ user password using shadow hash.")
#
def dscl_set_home
if new_resource.home.nil? || new_resource.home.empty?
- run_dscl("delete /Users/#{new_resource.username} NFSHomeDirectory")
+ run_dscl("delete", "/Users/#{new_resource.username}", "NFSHomeDirectory")
return
end
- if new_resource.supports[:manage_home]
+ if new_resource.manage_home
validate_home_dir_specification!
if (current_resource.home == new_resource.home) && !new_home_exists?
@@ -303,37 +287,34 @@ 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 =~ /^\//
+ unless %r{^/}.match?(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}")
+ !!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!("/usr/sbin/createhomedir", "-c", "-u", (new_resource.username).to_s)
end
def move_home
- Chef::Log.debug("#{new_resource} moving #{self} home from #{current_resource.home} to #{new_resource.home}")
-
+ logger.trace("#{new_resource} moving #{self} home from #{current_resource.home} to #{new_resource.home}")
+ new_resource.gid(STAFF_GROUP_ID) if new_resource.gid.nil?
src = current_resource.home
FileUtils.mkdir_p(new_resource.home)
files = ::Dir.glob("#{Chef::Util::PathHelper.escape_glob_dir(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)
end
@@ -342,10 +323,10 @@ user password using shadow hash.")
# 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
+ 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
@@ -362,9 +343,8 @@ user password using shadow hash.")
# 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)
- command.run_command
+ shell_out("plutil", "-convert", "binary1", "-o", "-", "-",
+ input: shadow_info.to_plist, live_stream: shadow_info_binary)
if user_info.nil?
# User is just created. read_user_info() will read the fresh information
@@ -389,47 +369,31 @@ user password using shadow hash.")
salt = nil
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
-
- shadow_info["SALTED-SHA512"] = StringIO.new
- shadow_info["SALTED-SHA512"].string = convert_to_binary(hash_value)
- shadow_info
+ 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
- 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
-
- entropy = OpenSSL::PKCS5.pbkdf2_hmac(
- new_resource.password,
- salt,
- iterations,
- 128,
- OpenSSL::Digest::SHA512.new
- )
- end
-
- pbkdf_info = {}
- pbkdf_info["entropy"] = StringIO.new
- pbkdf_info["entropy"].string = entropy
- pbkdf_info["salt"] = StringIO.new
- pbkdf_info["salt"].string = salt
- pbkdf_info["iterations"] = iterations
-
- shadow_info["SALTED-SHA512-PBKDF2"] = pbkdf_info
+ salt = OpenSSL::Random.random_bytes(32)
+ iterations = new_resource.iterations # Use the default if not specified by the user
+
+ entropy = OpenSSL::PKCS5.pbkdf2_hmac(
+ new_resource.password,
+ salt,
+ iterations,
+ 128,
+ OpenSSL::Digest.new("SHA512")
+ )
end
+ pbkdf_info = {}
+ pbkdf_info["entropy"] = StringIO.new
+ pbkdf_info["entropy"].string = entropy
+ pbkdf_info["salt"] = StringIO.new
+ pbkdf_info["salt"].string = salt
+ pbkdf_info["iterations"] = iterations
+
+ shadow_info["SALTED-SHA512-PBKDF2"] = pbkdf_info
shadow_info
end
@@ -438,27 +402,27 @@ user password using shadow hash.")
# and deleting home directory if needed.
#
def remove_user
- if new_resource.supports[:manage_home]
+ if new_resource.manage_home
# Remove home directory
FileUtils.rm_rf(current_resource.home)
end
# Remove the user from its groups
- run_dscl("list /Groups").each_line do |group|
+ 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
#
@@ -466,7 +430,7 @@ user password using shadow hash.")
#
def unlock_user
auth_string = authentication_authority.gsub(/AuthenticationAuthority: /, "").gsub(/;DisabledUser;/, "").strip
- run_dscl("create /Users/#{new_resource.username} AuthenticationAuthority '#{auth_string}'")
+ run_dscl("create", "/Users/#{new_resource.username}", "AuthenticationAuthority", auth_string)
end
#
@@ -474,7 +438,7 @@ user password using shadow hash.")
#
def locked?
if authentication_authority
- !!(authentication_authority =~ /DisabledUser/ )
+ !!(authentication_authority.include?("DisabledUser"))
else
false
end
@@ -484,7 +448,7 @@ user password using shadow hash.")
# This is the interface base User provider requires to provide idempotency.
#
def check_lock
- return @locked = locked?
+ @locked = locked?
end
#
@@ -496,11 +460,11 @@ user password using shadow hash.")
# given attribute.
#
def diverged?(parameter)
- parameter_updated?(parameter) && (not new_resource.send(parameter).nil?)
+ parameter_updated?(parameter) && !new_resource.send(parameter).nil?
end
def parameter_updated?(parameter)
- not (new_resource.send(parameter) == current_resource.send(parameter))
+ !(new_resource.send(parameter) == current_resource.send(parameter))
end
#
@@ -514,29 +478,16 @@ user password using shadow hash.")
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)
- diverged?(:password)
- else
- !salted_sha512_password_match?
- end
+ #
+ # 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?
+
+ if salted_sha512_pbkdf2?(new_resource.password)
+ diverged?(:password) || diverged?(:salt) || diverged?(:iterations)
else
- # When a system is upgraded to a version 10.7+ shadow hashes of the users
- # 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)
-
- # 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?
-
- if salted_sha512_pbkdf2?(new_resource.password)
- diverged?(:password) || diverged?(:salt) || diverged?(:iterations)
- else
- !salted_sha512_pbkdf2_password_match?
- end
+ !salted_sha512_pbkdf2_password_match?
end
end
@@ -546,7 +497,7 @@ user password using shadow hash.")
def member_of_group?(group_name)
membership_info = ""
begin
- membership_info = run_dscl("read /Groups/#{group_name}")
+ membership_info = run_dscl("read", "/Groups/#{group_name}")
rescue Chef::Exceptions::DsclCommandFailed
# Raised if the group doesn't contain any members
end
@@ -563,14 +514,14 @@ user password using shadow hash.")
# A simple map of Chef's terms to DSCL's terms.
DSCL_PROPERTY_MAP = {
- :uid => "uid",
- :gid => "gid",
- :home => "home",
- :shell => "shell",
- :comment => "realname",
- :password => "passwd",
- :auth_authority => "authentication_authority",
- :shadow_hash => "ShadowHashData",
+ uid: "uid",
+ gid: "gid",
+ home: "home",
+ shell: "shell",
+ comment: "realname",
+ password: "passwd",
+ auth_authority: "authentication_authority",
+ shadow_hash: "ShadowHashData",
}.freeze
# Directory where the user plist files are stored for versions 10.7 and above
@@ -585,12 +536,12 @@ user password using shadow hash.")
# We flush the cache here in order to make sure that we read fresh information
# for the user.
- shell_out("dscacheutil '-flushcache'")
+ shell_out("dscacheutil", "-flushcache") # FIXME: this is macOS version dependent
begin
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)
+ user_plist_info = run_plutil("convert", "xml1", "-o", "-", user_plist_file)
+ user_info = ::Plist.parse_xml(user_plist_info)
rescue Chef::Exceptions::PlistUtilCommandFailed
end
@@ -603,15 +554,16 @@ user password using shadow hash.")
#
def save_user_info(user_info)
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}")
+ ::Plist::Emit.save_plist(user_info, user_plist_file)
+ run_plutil("convert", "binary1", user_plist_file)
end
#
# Sets a value in user information hash using Chef attributes as keys.
#
def dscl_set(user_hash, key, value)
- raise "Unknown dscl key #{key}" unless DSCL_PROPERTY_MAP.keys.include?(key)
+ raise "Unknown dscl key #{key}" unless DSCL_PROPERTY_MAP.key?(key)
+
user_hash[DSCL_PROPERTY_MAP[key]] = [ value ]
user_hash
end
@@ -620,58 +572,39 @@ user password using shadow hash.")
# Gets a value from user information hash using Chef attributes as keys.
#
def dscl_get(user_hash, key)
- raise "Unknown dscl key #{key}" unless DSCL_PROPERTY_MAP.keys.include?(key)
+ raise "Unknown dscl key #{key}" unless DSCL_PROPERTY_MAP.key?(key)
+
# DSCL values are set as arrays
value = user_hash[DSCL_PROPERTY_MAP[key]]
value.nil? ? value : value.first
end
#
- # System Helpets
+ # System Helpers
#
- def mac_osx_version
- # This provider will only be invoked on node[:platform] == "mac_os_x"
- # We do not check or assert that here.
- node[:platform_version]
- end
-
- def mac_osx_version_10_7?
- mac_osx_version.start_with?("10.7.")
- end
-
- def mac_osx_version_less_than_10_7?
- versions = mac_osx_version.split(".")
- # Make integer comparison in order not to report 10.10 less than 10.7
- (versions[0].to_i <= 10 && versions[1].to_i < 7)
- end
-
- def mac_osx_version_greater_than_10_7?
- versions = mac_osx_version.split(".")
- # Make integer comparison in order not to report 10.10 less than 10.7
- (versions[0].to_i >= 10 && versions[1].to_i > 7)
- end
-
def run_dscl(*args)
- result = shell_out("dscl . -#{args.join(' ')}")
+ result = shell_out("dscl", ".", "-#{args[0]}", args[1..])
return "" if ( args.first =~ /^delete/ ) && ( result.exitstatus != 0 )
raise(Chef::Exceptions::DsclCommandFailed, "dscl error: #{result.inspect}") unless result.exitstatus == 0
- raise(Chef::Exceptions::DsclCommandFailed, "dscl error: #{result.inspect}") if result.stdout =~ /No such key: /
+ raise(Chef::Exceptions::DsclCommandFailed, "dscl error: #{result.inspect}") if result.stdout.include?("No such key: ")
+
result.stdout
end
def run_plutil(*args)
- result = shell_out("plutil -#{args.join(' ')}")
+ result = shell_out("plutil", "-#{args[0]}", args[1..])
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
end
def convert_binary_plist_to_xml(binary_plist_string)
- Mixlib::ShellOut.new("plutil -convert xml1 -o - -", :input => binary_plist_string).run_command.stdout
+ shell_out("plutil", "-convert", "xml1", "-o", "-", "-", input: binary_plist_string).stdout
end
def convert_to_binary(string)
@@ -682,13 +615,6 @@ user password using shadow hash.")
!!(string =~ /^[[:xdigit:]]{136}$/)
end
- 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
- end
-
def salted_sha512_pbkdf2?(string)
!!(string =~ /^[[:xdigit:]]{256}$/)
end
@@ -701,7 +627,7 @@ user password using shadow hash.")
salt,
current_resource.iterations,
128,
- OpenSSL::Digest::SHA512.new
+ OpenSSL::Digest.new("SHA512")
).unpack("H*").first == current_resource.password
end
diff --git a/lib/chef/provider/user/linux.rb b/lib/chef/provider/user/linux.rb
index 4a2491d806..40b5985cb1 100644
--- a/lib/chef/provider/user/linux.rb
+++ b/lib/chef/provider/user/linux.rb
@@ -1,5 +1,5 @@
#
-# Copyright:: Copyright 2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require "chef/provider/user"
+require_relative "../user"
class Chef
class Provider
@@ -24,23 +24,28 @@ class Chef
provides :user, os: "linux"
def create_user
- shell_out!(*clean_array("useradd", universal_options, useradd_options, new_resource.username))
+ shell_out!("useradd", universal_options, useradd_options, new_resource.username)
end
def manage_user
- shell_out!(*clean_array("usermod", universal_options, usermod_options, new_resource.username))
+ manage_u = shell_out("usermod", universal_options, usermod_options, new_resource.username, returns: [0, 12])
+ if manage_u.exitstatus == 12 && manage_u.stderr !~ /exists/
+ raise Chef::Exceptions::User, "Unable to modify home directory for #{new_resource.username}"
+ end
+
+ manage_u.error!
end
def remove_user
- shell_out!(*clean_array("userdel", userdel_options, new_resource.username))
+ shell_out!("userdel", userdel_options, new_resource.username)
end
def lock_user
- shell_out!(*clean_array("usermod", "-L", new_resource.username))
+ shell_out!("usermod", "-L", new_resource.username)
end
def unlock_user
- shell_out!(*clean_array("usermod", "-U", new_resource.username))
+ shell_out!("usermod", "-U", new_resource.username)
end
# common to usermod and useradd
@@ -52,14 +57,15 @@ class Chef
opts << "-s" << new_resource.shell if should_set?(:shell)
opts << "-u" << new_resource.uid if should_set?(:uid)
opts << "-d" << new_resource.home if updating_home?
- opts << "-o" if non_unique
+ opts << "-o" if new_resource.non_unique
opts
end
def usermod_options
opts = []
+ opts += [ "-u", new_resource.uid ] if new_resource.non_unique
if updating_home?
- if manage_home
+ if new_resource.manage_home
opts << "-m"
end
end
@@ -69,43 +75,31 @@ class Chef
def useradd_options
opts = []
opts << "-r" if new_resource.system
- if manage_home
- opts << "-m"
- else
- opts << "-M"
- end
+ opts << if new_resource.manage_home
+ "-m"
+ else
+ "-M"
+ end
opts
end
def userdel_options
opts = []
- opts << "-r" if manage_home
+ opts << "-r" if new_resource.manage_home
opts << "-f" if new_resource.force
opts
end
- def should_set?(sym)
- current_resource.send(sym).to_s != new_resource.send(sym).to_s && new_resource.send(sym)
- end
-
- def updating_home?
- return false unless new_resource.home
- return true unless current_resource.home
- new_resource.home && Pathname.new(current_resource.home).cleanpath != Pathname.new(new_resource.home).cleanpath
- end
-
def check_lock
# there's an old bug in rhel (https://bugzilla.redhat.com/show_bug.cgi?id=578534)
# which means that both 0 and 1 can be success.
passwd_s = shell_out("passwd", "-S", new_resource.username, returns: [ 0, 1 ])
# checking "does not exist" has to come before exit code handling since centos and ubuntu differ in exit codes
- if passwd_s.stderr =~ /does not exist/
- if whyrun_mode?
- return false
- else
- raise Chef::Exceptions::User, "User #{new_resource.username} does not exist when checking lock status for #{new_resource}"
- end
+ if /does not exist/.match?(passwd_s.stderr)
+ return false if whyrun_mode?
+
+ raise Chef::Exceptions::User, "User #{new_resource.username} does not exist when checking lock status for #{new_resource}"
end
# now raise if we didn't get a 0 or 1 (see above)
@@ -114,24 +108,14 @@ class Chef
# now the actual output parsing
@locked = nil
status_line = passwd_s.stdout.split(" ")
- @locked = false if status_line[1] =~ /^[PN]/
- @locked = true if status_line[1] =~ /^L/
+ @locked = false if /^[PN]/.match?(status_line[1])
+ @locked = true if /^L/.match?(status_line[1])
raise Chef::Exceptions::User, "Cannot determine if user #{new_resource.username} is locked for #{new_resource}" if @locked.nil?
# FIXME: should probably go on the current_resource
@locked
end
-
- def non_unique
- # XXX: THIS GOES AWAY IN CHEF-13 AND BECOMES JUST new_resource.non_unique
- new_resource.non_unique || new_resource.supports[:non_unique]
- end
-
- def manage_home
- # XXX: THIS GOES AWAY IN CHEF-13 AND BECOMES JUST new_resource.manage_home
- new_resource.manage_home || new_resource.supports[:manage_home]
- end
end
end
end
diff --git a/lib/chef/provider/user/mac.rb b/lib/chef/provider/user/mac.rb
new file mode 100644
index 0000000000..5604244f7f
--- /dev/null
+++ b/lib/chef/provider/user/mac.rb
@@ -0,0 +1,680 @@
+#
+# Author:: Ryan Cragun (<ryan@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../../resource"
+require_relative "../../dsl/declare_resource"
+require_relative "../../mixin/shell_out"
+require_relative "../../mixin/which"
+require_relative "../user"
+require_relative "../../resource/user/mac_user"
+autoload :Plist, "plist"
+
+class Chef
+ class Provider
+ class User
+ # A macOS user provider that is compatible with default TCC restrictions
+ # in macOS 10.14. See resource/user/mac_user.rb for complete description
+ # of the mac_user resource and how it differs from the dscl resource used
+ # on previous platforms.
+ class MacUser < Chef::Provider::User
+ include Chef::Mixin::Which
+
+ provides :mac_user
+ provides :user, os: "darwin", platform_version: ">= 10.14"
+
+ attr_reader :user_plist, :admin_group_plist
+
+ def load_current_resource
+ @current_resource = Chef::Resource::User::MacUser.new(new_resource.username)
+ current_resource.username(new_resource.username)
+
+ reload_admin_group_plist
+ reload_user_plist
+
+ if user_plist
+ current_resource.uid(user_plist[:uid][0])
+ current_resource.gid(user_plist[:gid][0])
+ current_resource.home(user_plist[:home][0])
+ current_resource.shell(user_plist[:shell][0])
+ current_resource.comment(user_plist[:comment][0])
+
+ if user_plist[:is_hidden]
+ current_resource.hidden(user_plist[:is_hidden][0] == "1" ? true : false)
+ end
+
+ shadow_hash = user_plist[:shadow_hash]
+ if shadow_hash
+ current_resource.password(shadow_hash[0]["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack("H*")[0])
+ current_resource.salt(shadow_hash[0]["SALTED-SHA512-PBKDF2"]["salt"].string.unpack("H*")[0])
+ current_resource.iterations(shadow_hash[0]["SALTED-SHA512-PBKDF2"]["iterations"].to_i)
+ end
+
+ current_resource.secure_token(secure_token_enabled?)
+ current_resource.admin(admin_user?)
+ else
+ @user_exists = false
+ logger.trace("#{new_resource} user does not exist")
+ end
+
+ current_resource
+ end
+
+ def reload_admin_group_plist
+ @admin_group_plist = nil
+
+ admin_group_xml = run_dscl("read", "/Groups/admin")
+ return nil unless admin_group_xml && admin_group_xml != ""
+
+ @admin_group_plist = Plist.new(::Plist.parse_xml(admin_group_xml))
+ end
+
+ def reload_user_plist
+ @user_plist = nil
+
+ # Load the user information.
+ begin
+ user_xml = run_dscl("read", "/Users/#{new_resource.username}")
+ rescue Chef::Exceptions::DsclCommandFailed
+ return nil
+ end
+
+ return nil if user_xml.nil? || user_xml == ""
+
+ @user_plist = Plist.new(::Plist.parse_xml(user_xml))
+
+ return unless user_plist[:shadow_hash]
+
+ shadow_hash_hex = user_plist[:shadow_hash][0]
+ return unless shadow_hash_hex && shadow_hash_hex != ""
+
+ # The password information is stored in the ShadowHashData key in the
+ # plist. However, parsing it is a bit tricky as the value is itself
+ # another encoded binary plist. We have to extract the encoded plist,
+ # decode it from hex to a binary plist and then convert the binary
+ # into XML plist. From there we can extract the hash data.
+ #
+ # NOTE: `dscl -read` and `plutil -convert` return different values for
+ # ShadowHashData.
+ #
+ # `dscl` returns the value encoded as a hex string and stored as a <string>
+ # `plutil` returns the value encoded as a base64 string stored as <data>
+ #
+ # eg:
+ #
+ # spellchecker: disable
+ #
+ # <array>
+ # <string>77687920 63616e27 74206170 706c6520 6275696c 6420636f 6e736973 74656e74 20746f6f 6c696e67</string>
+ # </array>
+ #
+ # vs
+ #
+ # <array>
+ # <data>AADKAAAKAA4LAA0MAAAAAAAAAAA=</data>
+ # </array>
+ #
+ # spellchecker: disable
+ #
+ begin
+ shadow_binary_plist = [shadow_hash_hex.delete(" ")].pack("H*")
+ shadow_xml_plist = shell_out("plutil", "-convert", "xml1", "-o", "-", "-", input: shadow_binary_plist).stdout
+ user_plist[:shadow_hash] = ::Plist.parse_xml(shadow_xml_plist)
+ rescue Chef::Exceptions::PlistUtilCommandFailed, Chef::Exceptions::DsclCommandFailed
+ nil
+ end
+ end
+
+ #
+ # User Provider Callbacks
+ #
+
+ def create_user
+ cmd = [-"-addUser", new_resource.username]
+ cmd += ["-fullName", new_resource.comment] if prop_is_set?(:comment)
+ cmd += ["-UID", prop_is_set?(:uid) ? new_resource.uid : get_free_uid]
+ cmd += ["-shell", new_resource.shell]
+ cmd += ["-home", new_resource.home]
+ cmd += ["-admin"] if new_resource.admin
+
+ # We can technically create a new user without the admin credentials
+ # but without them the user cannot enable SecureToken, thus they cannot
+ # create other secure users or enable FileVault full disk encryption.
+ if prop_is_set?(:admin_username) && prop_is_set?(:admin_password)
+ cmd += ["-adminUser", new_resource.admin_username]
+ cmd += ["-adminPassword", new_resource.admin_password]
+ end
+
+ # sysadminctl doesn't exit with a non-zero exit code if it encounters
+ # a problem. We'll check stderr and make sure we see that it finished
+ # correctly.
+ res = run_sysadminctl(cmd)
+ unless /creating user/.match?(res.downcase)
+ raise Chef::Exceptions::User, "error when creating user: #{res}"
+ end
+
+ # Wait for the user to show up in the ds cache
+ wait_for_user
+
+ # Reload with up-to-date user information
+ reload_user_plist
+ reload_admin_group_plist
+
+ if prop_is_set?(:hidden)
+ set_hidden
+ end
+
+ if prop_is_set?(:password)
+ converge_by("set password") { set_password }
+ end
+
+ if new_resource.manage_home
+ # "sysadminctl -addUser" will create the home directory if it's
+ # the default /Users/<username>, otherwise it sets it in plist
+ # but does not create it. Here we'll ensure that it gets created
+ # if we've been given a directory that is not the default.
+ unless ::File.directory?(new_resource.home) && ::File.exist?(new_resource.home)
+ converge_by("create home directory") do
+ shell_out!("createhomedir -c -u #{new_resource.username}")
+ end
+ end
+ end
+
+ if prop_is_set?(:gid)
+ # NOTE: Here we're managing the primary group of the user which is
+ # a departure from previous behavior. We could just set the
+ # PrimaryGroupID for the user and move on if we decide that actual
+ # group management should be done outside of the core resource.
+ group_name, group_id, group_action = user_group_info
+
+ group group_name do
+ members new_resource.username
+ gid group_id if group_id
+ action group_action
+ append true
+ end
+
+ converge_by("create primary group ID") do
+ run_dscl("create", "/Users/#{new_resource.username}", "PrimaryGroupID", group_id)
+ end
+ end
+
+ if diverged?(:secure_token)
+ converge_by("alter SecureToken") { toggle_secure_token }
+ end
+
+ reload_user_plist
+ end
+
+ def compare_user
+ @change_desc = []
+ %i{comment shell uid gid salt password admin secure_token hidden}.each do |attr|
+ if diverged?(attr)
+ desc = "Update #{attr}"
+ unless %i{password gid secure_token hidden}.include?(attr)
+ desc << " from #{current_resource.send(attr)} to #{new_resource.send(attr)}"
+ end
+ @change_desc << desc
+ end
+ end
+ !@change_desc.empty?
+ end
+
+ def manage_user
+ %i{uid home}.each do |prop|
+ raise Chef::Exceptions::User, "cannot modify #{prop} on macOS >= 10.14" if diverged?(prop)
+ end
+
+ if diverged?(:password)
+ converge_by("alter password") { set_password }
+ end
+
+ if diverged?(:comment)
+ converge_by("alter comment") do
+ run_dscl("create", "/Users/#{new_resource.username}", "RealName", new_resource.comment)
+ end
+ end
+
+ if diverged?(:shell)
+ converge_by("alter shell") do
+ run_dscl("create", "/Users/#{new_resource.username}", "UserShell", new_resource.shell)
+ end
+ end
+
+ if diverged?(:secure_token)
+ converge_by("alter SecureToken") { toggle_secure_token }
+ end
+
+ if diverged?(:admin)
+ converge_by("alter admin group membership") do
+ group "admin" do
+ if new_resource.admin
+ members new_resource.username
+ else
+ excluded_members new_resource.username
+ end
+
+ action :create
+ append true
+ end
+
+ admins = admin_group_plist[:group_members]
+ if new_resource.admin
+ admins << user_plist[:guid][0]
+ else
+ admins.reject! { |m| m == user_plist[:guid][0] }
+ end
+
+ run_dscl("create", "/Groups/admin", "GroupMembers", admins)
+ end
+
+ reload_admin_group_plist
+ end
+
+ group_name, group_id, group_action = user_group_info
+ group group_name do
+ gid group_id if group_id
+ members new_resource.username
+ action group_action
+ append true
+ end
+
+ if diverged?(:gid)
+ converge_by("alter group membership") do
+ run_dscl("create", "/Users/#{new_resource.username}", "PrimaryGroupID", group_id)
+ end
+ end
+
+ if diverged?(:hidden)
+ converge_by("alter hidden") { set_hidden }
+ end
+
+ reload_user_plist
+ end
+
+ def remove_user
+ cmd = ["-deleteUser", new_resource.username]
+ cmd << new_resource.manage_home ? "-secure" : "-keepHome"
+ if %i{admin_username admin_password}.all? { |p| prop_is_set?(p) }
+ cmd += ["-adminUser", new_resource.admin_username]
+ cmd += ["-adminPassword", new_resource.admin_password]
+ end
+
+ # sysadminctl doesn't exit with a non-zero exit code if it encounters
+ # a problem. We'll check stderr and make sure we see that it finished
+ res = run_sysadminctl(cmd)
+ unless /deleting record|not found/.match?(res.downcase)
+ raise Chef::Exceptions::User, "error deleting user: #{res}"
+ end
+
+ reload_user_plist
+ @user_exists = false
+ end
+
+ def lock_user
+ run_dscl("append", "/Users/#{new_resource.username}", "AuthenticationAuthority", ";DisabledUser;")
+
+ reload_user_plist
+ end
+
+ def unlock_user
+ auth_string = user_plist[:auth_authority].reject! { |tag| tag == ";DisabledUser;" }.join.strip
+
+ run_dscl("create", "/Users/#{new_resource.username}", "AuthenticationAuthority", auth_string)
+
+ reload_user_plist
+ end
+
+ def locked?
+ user_plist[:auth_authority].any? { |tag| tag == ";DisabledUser;" }
+ rescue
+ false
+ end
+
+ def check_lock
+ @locked = locked?
+ end
+
+ #
+ # Methods
+ #
+
+ def diverged?(prop)
+ prop = prop.to_sym
+
+ case prop
+ when :password
+ password_diverged?
+ when :gid
+ user_group_diverged?
+ when :secure_token
+ secure_token_diverged?
+ when :hidden
+ hidden_diverged?
+ else
+ # Other fields are have been set on current resource so just compare
+ # them.
+ !new_resource.send(prop).nil? && (new_resource.send(prop) != current_resource.send(prop))
+ end
+ end
+
+ # Find the next available uid on the system.
+ # Starting with 200 if `system` is set, 501 otherwise.
+ def get_free_uid(search_limit = 1000)
+ uid = nil
+ base_uid = new_resource.system ? 200 : 501
+ next_uid_guess = base_uid
+ users_uids = run_dscl("list", "/Users", "uid")
+ while next_uid_guess < search_limit + base_uid
+ if users_uids&.match?(Regexp.new("#{Regexp.escape(next_uid_guess.to_s)}\n"))
+ next_uid_guess += 1
+ else
+ uid = next_uid_guess
+ break
+ end
+ end
+ uid || raise("uid not found. Exhausted. Searched #{search_limit} times")
+ end
+
+ # Attempt to resolve the group name, gid, and the action required for
+ # associated group resource. If a group exists we'll modify it, otherwise
+ # create it.
+ def user_group_info
+ @user_group_info ||= begin
+ if new_resource.gid.is_a?(String)
+ begin
+ g = Etc.getgrnam(new_resource.gid)
+ [g.name, g.gid.to_s, :modify]
+ rescue
+ [new_resource.gid, nil, :create]
+ end
+ else
+ begin
+ g = Etc.getgrgid(new_resource.gid)
+ [g.name, g.gid.to_s, :modify]
+ rescue
+ [g.username, nil, :create]
+ end
+ end
+ end
+ end
+
+ def secure_token_enabled?
+ user_plist[:auth_authority].any? { |tag| tag == ";SecureToken;" }
+ rescue
+ false
+ end
+
+ def secure_token_diverged?
+ new_resource.secure_token ? !secure_token_enabled? : secure_token_enabled?
+ end
+
+ def toggle_secure_token
+ # Check for this lazily as we only need to validate for these credentials
+ # if we're toggling secure token.
+ unless %i{admin_username admin_password secure_token_password}.all? { |p| prop_is_set?(p) }
+ raise Chef::Exceptions::User, "secure_token_password, admin_username and admin_password properties are required to modify SecureToken"
+ end
+
+ cmd = (new_resource.secure_token ? %w{-secureTokenOn} : %w{-secureTokenOff})
+ cmd += [new_resource.username, "-password", new_resource.secure_token_password]
+ cmd += ["-adminUser", new_resource.admin_username]
+ cmd += ["-adminPassword", new_resource.admin_password]
+
+ # sysadminctl doesn't exit with a non-zero exit code if it encounters
+ # a problem. We'll check stderr and make sure we see that it finished
+ res = run_sysadminctl(cmd)
+ unless /done/.match?(res.downcase)
+ raise Chef::Exceptions::User, "error when modifying SecureToken: #{res}"
+ end
+
+ # HACK: When SecureToken is enabled or disabled it requires the user
+ # password in plaintext, which it verifies and uses as a key. It also
+ # takes the liberty of _rehashing_ the password with a random salt and
+ # iterations count and saves it back into the user ShadowHashData.
+ #
+ # Therefore, if we're configuring a user based upon existing shadow
+ # hash data we'll have to set the password again so that future runs
+ # of the client don't show password drift.
+ set_password if prop_is_set?(:salt)
+ end
+
+ def user_group_diverged?
+ return false unless prop_is_set?(:gid)
+
+ group_name, group_id = user_group_info
+ current_resource.gid != group_id.to_i
+ end
+
+ def hidden_diverged?
+ return false unless prop_is_set?(:hidden)
+
+ (current_resource.hidden ? 1 : 0) != hidden_value.to_i
+ end
+
+ def set_hidden
+ run_dscl("create", "/Users/#{new_resource.username}", "IsHidden", hidden_value.to_i)
+ end
+
+ def hidden_value
+ new_resource.hidden ? 1 : 0
+ end
+
+ def password_diverged?
+ # There are three options for configuring the password:
+ # * ShadowHashData which includes the hash data as:
+ # * hashed entropy as the "password"
+ # * salt
+ # * iterations
+ # * Plaintext password
+ # * Not configuring it
+
+ # Check for no desired password configuration
+ return false unless prop_is_set?(:password)
+
+ # Check for ShadowHashData divergence by comparing the entropy,
+ # salt, and iterations.
+ if prop_is_set?(:salt)
+ return true if %i{salt iterations}.any? { |prop| diverged?(prop) }
+
+ return new_resource.password != current_resource.password
+ end
+
+ # Check for plaintext password divergence. We don't actually know
+ # what the stored password is but we can hash the given password with
+ # stored salt and iterations, and compare the resulting entropy with
+ # the saved entropy.
+ OpenSSL::PKCS5.pbkdf2_hmac(
+ new_resource.password,
+ convert_to_binary(current_resource.salt),
+ current_resource.iterations.to_i,
+ 128,
+ OpenSSL::Digest.new("SHA512")
+ ).unpack("H*")[0] != current_resource.password
+ end
+
+ def admin_user?
+ admin_group_plist[:group_members].any? { |mem| mem == user_plist[:guid][0] }
+ rescue
+ false
+ end
+
+ def convert_to_binary(string)
+ string.unpack("a2" * (string.size / 2)).collect { |i| i.hex.chr }.join
+ end
+
+ def set_password
+ if prop_is_set?(:salt)
+ entropy = StringIO.new(convert_to_binary(new_resource.password))
+ salt = StringIO.new(convert_to_binary(new_resource.salt))
+ else
+ salt = StringIO.new(OpenSSL::Random.random_bytes(32))
+ entropy = StringIO.new(
+ OpenSSL::PKCS5.pbkdf2_hmac(
+ new_resource.password,
+ salt.string,
+ new_resource.iterations,
+ 128,
+ OpenSSL::Digest.new("SHA512")
+ )
+ )
+ end
+
+ shadow_hash = user_plist[:shadow_hash] ? user_plist[:shadow_hash][0] : {}
+ shadow_hash["SALTED-SHA512-PBKDF2"] = {
+ "entropy" => entropy,
+ "salt" => salt,
+ "iterations" => new_resource.iterations,
+ }
+
+ shadow_hash_binary = StringIO.new
+ shell_out("plutil", "-convert", "binary1", "-o", "-", "-",
+ input: shadow_hash.to_plist,
+ live_stream: shadow_hash_binary)
+
+ # Apple seem to have killed their dsimport documentation about the
+ # dsimport record format. Perhaps that means our days of being able to
+ # use dsimport without an admin password or perhaps at all could be
+ # numbered. Here is the record format for posterity:
+ #
+ # End of record character
+ # Escape character
+ # Field separator
+ # Value separator
+ # Record type (Users, Groups, Computers, ComputerGroups, ComputerLists)
+ # Number of properties
+ # Property 1
+ # ...
+ # Property N
+ #
+ # The user password shadow data format breaks down as:
+ #
+ # 0x0A End of record denoted by \n
+ # 0x5C Escaping is denoted by \
+ # 0x3A Fields are separated by :
+ # 0x2C Values are separated by ,
+ # dsRecTypeStandard:Users The record type we're configuring
+ # 2 How many properties we're going to set
+ # dsAttrTypeStandard:RecordName Property 1: our users record name
+ # base64:dsAttrTypeNative:ShadowHashData Property 2: our shadow hash data
+
+ import_file = ::File.join(Chef::Config["file_cache_path"], "#{new_resource.username}_password_dsimport")
+ ::File.open(import_file, "w+", 0600) do |f|
+ f.write <<~DSIMPORT
+ 0x0A 0x5C 0x3A 0x2C dsRecTypeStandard:Users 2 dsAttrTypeStandard:RecordName base64:dsAttrTypeNative:ShadowHashData
+ #{new_resource.username}:#{::Base64.strict_encode64(shadow_hash_binary.string)}
+ DSIMPORT
+ end
+
+ run_dscl("delete", "/Users/#{new_resource.username}", "ShadowHashData")
+ run_dsimport(import_file, "/Local/Default", "M")
+ run_dscl("create", "/Users/#{new_resource.username}", "Password", "********")
+ ensure
+ ::File.delete(import_file) if import_file && ::File.exist?(import_file)
+ end
+
+ def wait_for_user
+ timeout = Time.now + 5
+
+ loop do
+
+ run_dscl("read", "/Users/#{new_resource.username}", "ShadowHashData")
+ break
+ rescue Chef::Exceptions::DsclCommandFailed => e
+ if Time.now < timeout
+ sleep 0.1
+ else
+ raise Chef::Exceptions::User, e.message
+ end
+
+ end
+ end
+
+ def run_dsimport(*args)
+ shell_out!("dsimport", args)
+ end
+
+ def run_sysadminctl(args)
+ # sysadminctl doesn't exit with a non-zero code when errors are encountered
+ # and outputs everything to STDERR instead of STDOUT and STDERR. Therefore we'll
+ # return the STDERR and let the caller handle it.
+ shell_out!("sysadminctl", args).stderr
+ end
+
+ def run_dscl(*args)
+ result = shell_out("dscl", "-plist", ".", "-#{args[0]}", args[1..])
+ return "" if ( args.first =~ /^delete/ ) && ( result.exitstatus != 0 )
+ raise(Chef::Exceptions::DsclCommandFailed, "dscl error: #{result.inspect}") unless result.exitstatus == 0
+ raise(Chef::Exceptions::DsclCommandFailed, "dscl error: #{result.inspect}") if /No such key: /.match?(result.stdout)
+
+ result.stdout
+ end
+
+ def run_plutil(*args)
+ result = shell_out("plutil", "-#{args[0]}", args[1..])
+ raise(Chef::Exceptions::PlistUtilCommandFailed, "plutil error: #{result.inspect}") unless result.exitstatus == 0
+
+ result.stdout
+ end
+
+ def prop_is_set?(prop)
+ v = new_resource.send(prop.to_sym)
+
+ !v.nil? && v != ""
+ end
+
+ class Plist
+ DSCL_PROPERTY_MAP = {
+ uid: "dsAttrTypeStandard:UniqueID",
+ guid: "dsAttrTypeStandard:GeneratedUID",
+ gid: "dsAttrTypeStandard:PrimaryGroupID",
+ home: "dsAttrTypeStandard:NFSHomeDirectory",
+ shell: "dsAttrTypeStandard:UserShell",
+ comment: "dsAttrTypeStandard:RealName",
+ password: "dsAttrTypeStandard:Password",
+ auth_authority: "dsAttrTypeStandard:AuthenticationAuthority",
+ shadow_hash: "dsAttrTypeNative:ShadowHashData",
+ group_members: "dsAttrTypeStandard:GroupMembers",
+ is_hidden: "dsAttrTypeNative:IsHidden",
+ }.freeze
+
+ attr_accessor :plist_hash, :property_map
+
+ def initialize(plist_hash = {}, property_map = DSCL_PROPERTY_MAP)
+ @plist_hash = plist_hash
+ @property_map = property_map
+ end
+
+ def get(key)
+ return nil unless property_map.key?(key)
+
+ plist_hash[property_map[key]]
+ end
+ alias_method :[], :get
+
+ def set(key, value)
+ return nil unless property_map.key?(key)
+
+ plist_hash[property_map[key]] = [ value ]
+ end
+ alias_method :[]=, :set
+
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/user/pw.rb b/lib/chef/provider/user/pw.rb
index a1d7671c28..9e21e5a816 100644
--- a/lib/chef/provider/user/pw.rb
+++ b/lib/chef/provider/user/pw.rb
@@ -1,6 +1,6 @@
#
# Author:: Stephen Haynes (<sh@nomitor.com>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/provider/user"
+require_relative "../user"
class Chef
class Provider
@@ -27,49 +27,45 @@ class Chef
def load_current_resource
super
- raise Chef::Exceptions::User, "Could not find binary /usr/sbin/pw for #{@new_resource}" unless ::File.exists?("/usr/sbin/pw")
+ raise Chef::Exceptions::User, "Could not find binary /usr/sbin/pw for #{new_resource}" unless ::File.exist?("/usr/sbin/pw")
end
def create_user
- command = "pw useradd"
- command << set_options
- run_command(:command => command)
+ shell_out!("pw", "useradd", set_options)
modify_password
end
def manage_user
- command = "pw usermod"
- command << set_options
- run_command(:command => command)
+ shell_out!("pw", "usermod", set_options)
modify_password
end
def remove_user
- command = "pw userdel #{@new_resource.username}"
- command << " -r" if @new_resource.supports[:manage_home]
- run_command(:command => command)
+ command = [ "pw", "userdel", new_resource.username ]
+ command << "-r" if new_resource.manage_home
+ shell_out!(command)
end
def check_lock
- case @current_resource.password
- when /^\*LOCKED\*/
- @locked = true
- else
- @locked = false
- end
+ @locked = case current_resource.password
+ when /^\*LOCKED\*/
+ true
+ else
+ false
+ end
@locked
end
def lock_user
- run_command(:command => "pw lock #{@new_resource.username}")
+ shell_out!("pw", "lock", new_resource.username)
end
def unlock_user
- run_command(:command => "pw unlock #{@new_resource.username}")
+ shell_out!("pw", "unlock", new_resource.username)
end
def set_options
- opts = " #{@new_resource.username}"
+ opts = [ new_resource.username ]
field_list = {
"comment" => "-c",
@@ -78,35 +74,30 @@ class Chef
"uid" => "-u",
"shell" => "-s",
}
- field_list.sort { |a, b| a[0] <=> b[0] }.each do |field, option|
+ field_list.sort_by { |a| a[0] }.each do |field, option|
field_symbol = field.to_sym
- if @current_resource.send(field_symbol) != @new_resource.send(field_symbol)
- if @new_resource.send(field_symbol)
- Chef::Log.debug("#{@new_resource} setting #{field} to #{@new_resource.send(field_symbol)}")
- opts << " #{option} '#{@new_resource.send(field_symbol)}'"
- end
+ next unless current_resource.send(field_symbol) != new_resource.send(field_symbol)
+
+ if new_resource.send(field_symbol)
+ logger.trace("#{new_resource} setting #{field} to #{new_resource.send(field_symbol)}")
+ opts << option
+ opts << new_resource.send(field_symbol)
end
end
- if @new_resource.supports[:manage_home]
- Chef::Log.debug("#{@new_resource} is managing the users home directory")
- opts << " -m"
+ if new_resource.manage_home
+ logger.trace("#{new_resource} is managing the users home directory")
+ opts << "-m"
end
opts
end
def modify_password
- if (not @new_resource.password.nil?) && (@current_resource.password != @new_resource.password)
- Chef::Log.debug("#{new_resource} updating password")
- command = "pw usermod #{@new_resource.username} -H 0"
- status = popen4(command, :waitlast => true) do |pid, stdin, stdout, stderr|
- stdin.puts "#{@new_resource.password}"
- end
-
- unless status.exitstatus == 0
- raise Chef::Exceptions::User, "pw failed - #{status.inspect}!"
- end
+ if !new_resource.password.nil? && (current_resource.password != new_resource.password)
+ logger.trace("#{new_resource} updating password")
+ command = "pw usermod #{new_resource.username} -H 0"
+ shell_out!(command, input: new_resource.password.to_s)
else
- Chef::Log.debug("#{new_resource} no change needed to password")
+ logger.trace("#{new_resource} no change needed to password")
end
end
end
diff --git a/lib/chef/provider/user/solaris.rb b/lib/chef/provider/user/solaris.rb
index 8d3df9e68b..abf6cd94c8 100644
--- a/lib/chef/provider/user/solaris.rb
+++ b/lib/chef/provider/user/solaris.rb
@@ -2,7 +2,7 @@
# Author:: Stephen Nelson-Smith (<sns@chef.io>)
# Author:: Jon Ramsey (<jonathon.ramsey@gmail.com>)
# Author:: Dave Eddy (<dave@daveeddy.com>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# Copyright:: Copyright 2015-2016, Dave Eddy
# License:: Apache License, Version 2.0
#
@@ -18,49 +18,42 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require "chef/provider/user/useradd"
+require_relative "../user"
class Chef
class Provider
class User
- class Solaris < Chef::Provider::User::Useradd
+ class Solaris < Chef::Provider::User
provides :solaris_user
- provides :user, os: %w{omnios solaris2}
- UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:shell, "-s"], [:uid, "-u"]]
+ provides :user, os: %w{openindiana illumos omnios solaris2 smartos}
- attr_writer :password_file
-
- def initialize(new_resource, run_context)
- @password_file = "/etc/shadow"
- super
- end
+ PASSWORD_FILE = "/etc/shadow".freeze
def create_user
- super
+ shell_out!("useradd", universal_options, useradd_options, new_resource.username)
manage_password
end
def manage_user
manage_password
- super
+ return if universal_options.empty? && usermod_options.empty?
+
+ shell_out!("usermod", universal_options, usermod_options, new_resource.username)
end
- def check_lock
- shadow_line = shell_out!("getent", "shadow", new_resource.username).stdout.strip rescue nil
+ def remove_user
+ shell_out!("userdel", userdel_options, new_resource.username)
+ end
- # if the command fails we return nil, this can happen if the user
- # in question doesn't exist
- return nil if shadow_line.nil?
+ def check_lock
+ user = IO.read(PASSWORD_FILE).match(/^#{Regexp.escape(new_resource.username)}:([^:]*):/)
- # convert "dave:NP:16507::::::\n" to "NP"
- fields = shadow_line.split(":")
+ # If we're in whyrun mode, and the user is not created, we assume it will be
+ return false if whyrun_mode? && user.nil?
- # '*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\*?/)
+ raise Chef::Exceptions::User, "Cannot determine if #{new_resource} is locked!" if user.nil?
- @locked
+ @locked = user[1].start_with?("*LK*")
end
def lock_user
@@ -73,19 +66,66 @@ class Chef
private
- def manage_password
- if @current_resource.password != @new_resource.password && @new_resource.password
- Chef::Log.debug("#{@new_resource} setting password to #{@new_resource.password}")
- write_shadow_file
+ def universal_options
+ opts = []
+ opts << "-c" << new_resource.comment if should_set?(:comment)
+ opts << "-g" << new_resource.gid if should_set?(:gid)
+ opts << "-s" << new_resource.shell if should_set?(:shell)
+ opts << "-u" << new_resource.uid if should_set?(:uid)
+ opts << "-d" << new_resource.home if updating_home?
+ opts << "-o" if new_resource.non_unique
+ if updating_home?
+ if new_resource.manage_home
+ logger.trace("#{new_resource} managing the users home directory")
+ opts << "-m"
+ else
+ logger.trace("#{new_resource} setting home to #{new_resource.home}")
+ end
+ end
+ opts
+ end
+
+ def usermod_options
+ opts = []
+ opts += [ "-u", new_resource.uid ] if new_resource.non_unique
+ if updating_home?
+ if new_resource.manage_home
+ opts << "-m"
+ end
end
+ opts
+ end
+
+ def userdel_options
+ opts = []
+ opts << "-r" if new_resource.manage_home
+ opts << "-f" if new_resource.force
+ opts
+ end
+
+ # Solaris does not support system users and has no '-r' option, solaris also
+ # lacks '-M' and defaults to no-manage-home.
+ def useradd_options
+ opts = []
+ opts << "-m" if new_resource.manage_home
+ opts
+ end
+
+ def manage_password
+ return unless current_resource.password != new_resource.password && new_resource.password
+
+ logger.trace("#{new_resource} setting password to #{new_resource.password}")
+ write_shadow_file
end
+ # XXX: this was straight copypasta'd back in 2013 and I don't think we've ever evaluated using
+ # a pipe to passwd(1) or evaluating modern ruby-shadow. See https://github.com/chef/chef/pull/721
def write_shadow_file
buffer = Tempfile.new("shadow", "/etc")
- ::File.open(@password_file) do |shadow_file|
+ ::File.open(PASSWORD_FILE) do |shadow_file|
shadow_file.each do |entry|
user = entry.split(":").first
- if user == @new_resource.username
+ if user == new_resource.username
buffer.write(updated_password(entry))
else
buffer.write(entry)
@@ -95,20 +135,20 @@ class Chef
buffer.close
# FIXME: mostly duplicates code with file provider deploying a file
- s = ::File.stat(@password_file)
- mode = s.mode & 07777
+ s = ::File.stat(PASSWORD_FILE)
+ mode = s.mode & 0o7777
uid = s.uid
gid = s.gid
FileUtils.chown uid, gid, buffer.path
FileUtils.chmod mode, buffer.path
- FileUtils.mv buffer.path, @password_file
+ FileUtils.mv buffer.path, PASSWORD_FILE
end
def updated_password(entry)
fields = entry.split(":")
- fields[1] = @new_resource.password
+ fields[1] = new_resource.password
fields[2] = days_since_epoch
fields.join(":")
end
diff --git a/lib/chef/provider/user/useradd.rb b/lib/chef/provider/user/useradd.rb
deleted file mode 100644
index 68b62812a7..0000000000
--- a/lib/chef/provider/user/useradd.rb
+++ /dev/null
@@ -1,164 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-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 "pathname"
-require "chef/provider/user"
-
-class Chef
- class Provider
- class User
- class Useradd < Chef::Provider::User
- # MAJOR XXX: this should become the base class of all Useradd providers instead of the linux implementation
-
- UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:password, "-p"], [:shell, "-s"], [:uid, "-u"]]
-
- def create_user
- command = compile_command("useradd") do |useradd|
- useradd.concat(universal_options)
- useradd.concat(useradd_options)
- end
- shell_out!(*command)
- end
-
- def manage_user
- unless universal_options.empty?
- command = compile_command("usermod") do |u|
- u.concat(universal_options)
- end
- shell_out!(*command)
- end
- end
-
- def remove_user
- command = [ "userdel" ]
- command << "-r" if managing_home_dir?
- command << "-f" if new_resource.force
- command << new_resource.username
- shell_out!(*command)
- end
-
- def check_lock
- # we can get an exit code of 1 even when it's successful on
- # rhel/centos (redhat bug 578534). See additional error checks below.
- passwd_s = shell_out!("passwd", "-S", new_resource.username, :returns => [0, 1])
- if whyrun_mode? && passwd_s.stdout.empty? && passwd_s.stderr.match(/does not exist/)
- # if we're in whyrun mode and the user is not yet created we assume it would be
- return false
- end
-
- raise Chef::Exceptions::User, "Cannot determine if #{@new_resource} is locked!" if passwd_s.stdout.empty?
-
- status_line = passwd_s.stdout.split(" ")
- case status_line[1]
- when /^P/
- @locked = false
- when /^N/
- @locked = false
- when /^L/
- @locked = true
- end
-
- unless passwd_s.exitstatus == 0
- raise_lock_error = false
- if %w{redhat centos}.include?(node[:platform])
- passwd_version_check = shell_out!("rpm -q passwd")
- passwd_version = passwd_version_check.stdout.chomp
-
- unless passwd_version == "passwd-0.73-1"
- raise_lock_error = true
- end
- else
- raise_lock_error = true
- end
-
- raise Chef::Exceptions::User, "Cannot determine if #{new_resource} is locked!" if raise_lock_error
- end
-
- @locked
- end
-
- def lock_user
- shell_out!("usermod", "-L", new_resource.username)
- end
-
- def unlock_user
- shell_out!("usermod", "-U", new_resource.username)
- end
-
- def compile_command(base_command)
- base_command = Array(base_command)
- yield base_command
- base_command << new_resource.username
- base_command
- end
-
- def universal_options
- @universal_options ||=
- begin
- opts = []
- # magic allows UNIVERSAL_OPTIONS to be overridden in a subclass
- self.class::UNIVERSAL_OPTIONS.each do |field, option|
- update_options(field, option, opts)
- end
- if updating_home?
- opts << "-d" << new_resource.home
- if managing_home_dir?
- Chef::Log.debug("#{new_resource} managing the users home directory")
- opts << "-m"
- else
- Chef::Log.debug("#{new_resource} setting home to #{new_resource.home}")
- end
- end
- opts << "-o" if new_resource.non_unique
- opts
- end
- end
-
- def update_options(field, option, opts)
- if @current_resource.send(field).to_s != new_resource.send(field).to_s
- if new_resource.send(field)
- Chef::Log.debug("#{new_resource} setting #{field} to #{new_resource.send(field)}")
- opts << option << new_resource.send(field).to_s
- end
- end
- end
-
- def useradd_options
- opts = []
- opts << "-r" if new_resource.system
- opts << "-M" unless managing_home_dir?
- opts
- end
-
- def updating_home?
- # will return false if paths are equivalent
- # Pathname#cleanpath does a better job than ::File::expand_path (on both unix and windows)
- # ::File.expand_path("///tmp") == ::File.expand_path("/tmp") => false
- # ::File.expand_path("\\tmp") => "C:/tmp"
- return true if @current_resource.home.nil? && new_resource.home
- new_resource.home && Pathname.new(@current_resource.home).cleanpath != Pathname.new(new_resource.home).cleanpath
- end
-
- def managing_home_dir?
- new_resource.manage_home || new_resource.supports[:manage_home]
- end
-
- end
- end
- end
-end
diff --git a/lib/chef/provider/user/windows.rb b/lib/chef/provider/user/windows.rb
index b086a1e32b..32b2c35264 100644
--- a/lib/chef/provider/user/windows.rb
+++ b/lib/chef/provider/user/windows.rb
@@ -1,6 +1,6 @@
#
# Author:: Doug MacEachern (<dougm@vmware.com>)
-# Copyright:: Copyright 2010-2016, VMware, Inc.
+# Copyright:: Copyright 2010-2019, VMware, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,11 +16,9 @@
# limitations under the License.
#
-require "chef/provider/user"
-require "chef/exceptions"
-if RUBY_PLATFORM =~ /mswin|mingw32|windows/
- require "chef/util/windows/net_user"
-end
+require_relative "../user"
+require_relative "../../exceptions"
+require_relative "../../util/windows/net_user" if Chef::Platform.windows?
class Chef
class Provider
@@ -31,31 +29,30 @@ class Chef
def initialize(new_resource, run_context)
super
- @net_user = Chef::Util::Windows::NetUser.new(@new_resource.username)
+ @net_user = Chef::Util::Windows::NetUser.new(new_resource.username)
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.")
+ if new_resource.gid
+ logger.warn("The 'gid' (or 'group') property is not implemented on the Windows platform. Please use the `members` property of 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
+ @current_resource = Chef::Resource::User::WindowsUser.new(new_resource.name)
+ current_resource.username(new_resource.username)
begin
user_info = @net_user.get_info
-
- @current_resource.uid(user_info[:user_id])
- @current_resource.comment(user_info[:full_name])
- @current_resource.home(user_info[:home_dir])
- @current_resource.shell(user_info[:script_path])
+ current_resource.uid(user_info[:user_id])
+ current_resource.full_name(user_info[:full_name])
+ current_resource.comment(user_info[:comment])
+ current_resource.home(user_info[:home_dir])
+ current_resource.shell(user_info[:script_path])
rescue Chef::Exceptions::UserIDNotFound => e
# e.message should be "The user name could not be found" but checking for that could cause a localization bug
@user_exists = false
- Chef::Log.debug("#{@new_resource} does not exist (#{e.message})")
+ logger.trace("#{new_resource} does not exist (#{e.message})")
end
- @current_resource
+ current_resource
end
# Check to see if the user needs any changes
@@ -64,13 +61,20 @@ class Chef
# <true>:: If a change is required
# <false>:: If the users are identical
def compare_user
- unless @net_user.validate_credentials(@new_resource.password)
- Chef::Log.debug("#{@new_resource} password has changed")
- return true
+ @change_desc = []
+ unless @net_user.validate_credentials(new_resource.password)
+ @change_desc << "update password"
end
- [ :uid, :comment, :home, :shell ].any? do |user_attrib|
- !@new_resource.send(user_attrib).nil? && @new_resource.send(user_attrib) != @current_resource.send(user_attrib)
+
+ %i{uid comment home shell full_name}.any? do |user_attrib|
+ new_val = new_resource.send(user_attrib)
+ cur_val = current_resource.send(user_attrib)
+ if !new_val.nil? && new_val != cur_val
+ @change_desc << "change #{user_attrib} from #{cur_val} to #{new_val}"
+ end
end
+
+ !@change_desc.empty?
end
def create_user
@@ -98,26 +102,26 @@ class Chef
end
def set_options
- opts = { :name => @new_resource.username }
+ opts = { name: new_resource.username }
field_list = {
- "comment" => "full_name",
+ "full_name" => "full_name",
+ "comment" => "comment",
"home" => "home_dir",
"uid" => "user_id",
"shell" => "script_path",
"password" => "password",
}
- field_list.sort { |a, b| a[0] <=> b[0] }.each do |field, option|
+ field_list.sort_by { |a| a[0] }.each do |field, option|
field_symbol = field.to_sym
- if @current_resource.send(field_symbol) != @new_resource.send(field_symbol)
- if @new_resource.send(field_symbol)
- unless field_symbol == :password
- Chef::Log.debug("#{@new_resource} setting #{field} to #{@new_resource.send(field_symbol)}")
- end
- opts[option.to_sym] = @new_resource.send(field_symbol)
- end
+ next unless current_resource.send(field_symbol) != new_resource.send(field_symbol)
+ next unless new_resource.send(field_symbol)
+
+ unless field_symbol == :password
+ logger.trace("#{new_resource} setting #{field} to #{new_resource.send(field_symbol)}")
end
+ opts[option.to_sym] = new_resource.send(field_symbol)
end
opts
end
diff --git a/lib/chef/provider/whyrun_safe_ruby_block.rb b/lib/chef/provider/whyrun_safe_ruby_block.rb
index 3ea48017b7..e261cb4386 100644
--- a/lib/chef/provider/whyrun_safe_ruby_block.rb
+++ b/lib/chef/provider/whyrun_safe_ruby_block.rb
@@ -21,11 +21,11 @@ class Chef
class WhyrunSafeRubyBlock < Chef::Provider::RubyBlock
provides :whyrun_safe_ruby_block
- def action_run
- @new_resource.block.call
- @new_resource.updated_by_last_action(true)
- @run_context.events.resource_update_applied(@new_resource, :create, "execute the whyrun_safe_ruby_block #{@new_resource.name}")
- Chef::Log.info("#{@new_resource} called")
+ action :run do
+ new_resource.block.call
+ new_resource.updated_by_last_action(true)
+ @run_context.events.resource_update_applied(new_resource, :create, "execute the whyrun_safe_ruby_block #{new_resource.name}")
+ logger.info("#{new_resource} called")
end
end
end
diff --git a/lib/chef/provider/windows_script.rb b/lib/chef/provider/windows_script.rb
index 3b0202790c..a93319a35a 100644
--- a/lib/chef/provider/windows_script.rb
+++ b/lib/chef/provider/windows_script.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Edwards (<adamed@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,59 +16,121 @@
# limitations under the License.
#
-require "chef/provider/script"
-require "chef/mixin/windows_architecture_helper"
+require_relative "script"
+require_relative "../mixin/windows_architecture_helper"
+require_relative "../win32/security" if ChefUtils.windows?
+require "tempfile" unless defined?(Tempfile)
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 = "")
- super( new_resource, run_context )
- @script_extension = script_extension
-
- target_architecture = if new_resource.architecture.nil?
- node_windows_architecture(run_context.node)
- else
- new_resource.architecture
- end
+ attr_accessor :script_file_path
- @is_wow64 = wow64_architecture_override_required?(run_context.node, target_architecture)
+ include Chef::Mixin::WindowsArchitectureHelper
- @is_forced_32bit = forced_32bit_override_required?(run_context.node, target_architecture)
+ def target_architecture
+ @target_architecture ||= if new_resource.architecture.nil?
+ node_windows_architecture(run_context.node)
+ else
+ new_resource.architecture
+ end
end
- public
+ def basepath
+ if forced_32bit_override_required?(run_context.node, target_architecture)
+ wow64_directory
+ else
+ run_context.node["kernel"]["os_info"]["system_directory"]
+ end
+ end
- def action_run
+ def with_wow64_redirection_disabled
wow64_redirection_state = nil
- if @is_wow64
- wow64_redirection_state = disable_wow64_file_redirection(@run_context.node)
+ if wow64_architecture_override_required?(run_context.node, target_architecture)
+ wow64_redirection_state = disable_wow64_file_redirection(run_context.node)
end
begin
- super
+ yield
rescue
raise
ensure
- if ! wow64_redirection_state.nil?
- restore_wow64_file_redirection(@run_context.node, wow64_redirection_state)
+ unless wow64_redirection_state.nil?
+ restore_wow64_file_redirection(run_context.node, wow64_redirection_state)
end
end
end
- def script_file
- base_script_name = "chef-script"
- temp_file_arguments = [ base_script_name, @script_extension ]
+ def command
+ "\"#{interpreter}\" #{flags} \"#{script_file_path}\""
+ end
+
+ def grant_alternate_user_read_access(file_path)
+ # Do nothing if an alternate user isn't specified -- the file
+ # will already have the correct permissions for the user as part
+ # of the default ACL behavior on Windows.
+ return if new_resource.user.nil?
+
+ # Duplicate the script file's existing DACL
+ # so we can add an ACE later
+ securable_object = Chef::ReservedNames::Win32::Security::SecurableObject.new(file_path)
+ aces = securable_object.security_descriptor.dacl.reduce([]) { |result, current| result.push(current) }
+
+ username = new_resource.user
+
+ if new_resource.domain
+ username = new_resource.domain + '\\' + new_resource.user
+ end
+
+ # Create an ACE that allows the alternate user read access to the script
+ # file so it can be read and executed.
+ user_sid = Chef::ReservedNames::Win32::Security::SID.from_account(username)
+ read_ace = Chef::ReservedNames::Win32::Security::ACE.access_allowed(user_sid, Chef::ReservedNames::Win32::API::Security::GENERIC_READ | Chef::ReservedNames::Win32::API::Security::GENERIC_EXECUTE, 0)
+ aces.push(read_ace)
+ acl = Chef::ReservedNames::Win32::Security::ACL.create(aces)
+
+ # This actually applies the modified DACL to the file
+ # Use parentheses to bypass RuboCop / ChefStyle warning
+ # about useless setter
+ (securable_object.dacl = acl)
+ end
+
+ def with_temp_script_file
+ Tempfile.open(["chef-script", script_extension]) do |script_file|
+ script_file.puts(code)
+ script_file.close
+
+ grant_alternate_user_read_access(script_file.path)
+
+ # This needs to be set here so that the call to #command in Execute works.
+ self.script_file_path = script_file.path
+
+ yield
+
+ self.script_file_path = nil
+ end
+ end
+
+ def input
+ nil
+ end
+
+ public
+
+ action :run do
+ with_wow64_redirection_disabled do
+ with_temp_script_file do
+ super()
+ end
+ end
+ end
- @script_file ||= Tempfile.open(temp_file_arguments)
+ def script_extension
+ raise Chef::Exceptions::Override, "You must override #{__method__} in #{self}"
end
end
end
diff --git a/lib/chef/provider/yum_repository.rb b/lib/chef/provider/yum_repository.rb
index 09ff2c5512..b0dfe20f1b 100644
--- a/lib/chef/provider/yum_repository.rb
+++ b/lib/chef/provider/yum_repository.rb
@@ -1,6 +1,6 @@
#
# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright (c) 2016 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,34 +16,28 @@
# limitations under the License.
#
-require "chef/resource"
-require "chef/dsl/declare_resource"
-require "chef/mixin/shell_out"
-require "chef/mixin/which"
-require "chef/http/simple"
-require "chef/provider/noop"
+require_relative "../resource"
+require_relative "../dsl/declare_resource"
+require_relative "../mixin/which"
+require_relative "noop"
class Chef
class Provider
class YumRepository < Chef::Provider
- use_inline_resources
-
extend Chef::Mixin::Which
provides :yum_repository do
which "yum"
end
- def whyrun_supported?; true; end
-
def load_current_resource; end
action :create do
- declare_resource(:template, "/etc/yum.repos.d/#{new_resource.repositoryid}.repo") do
+ declare_resource(:template, ::File.join(new_resource.reposdir, "#{new_resource.repositoryid}.repo")) do
if template_available?(new_resource.source)
source new_resource.source
else
- source ::File.expand_path("../support/yum_repo.erb", __FILE__)
+ source ::File.expand_path("support/yum_repo.erb", __dir__)
local true
end
sensitive new_resource.sensitive
@@ -52,7 +46,7 @@ class Chef
if new_resource.make_cache
notifies :run, "execute[yum clean metadata #{new_resource.repositoryid}]", :immediately if new_resource.clean_metadata || new_resource.clean_headers
notifies :run, "execute[yum-makecache-#{new_resource.repositoryid}]", :immediately
- notifies :create, "ruby_block[yum-cache-reload-#{new_resource.repositoryid}]", :immediately
+ notifies :create, "ruby_block[package-cache-reload-#{new_resource.repositoryid}]", :immediately
end
end
@@ -68,28 +62,37 @@ class Chef
only_if { new_resource.enabled }
end
- # reload internal Chef yum cache
- declare_resource(:ruby_block, "yum-cache-reload-#{new_resource.repositoryid}") do
- block { Chef::Provider::Package::Yum::YumCache.instance.reload }
+ # reload internal Chef yum/dnf cache
+ declare_resource(:ruby_block, "package-cache-reload-#{new_resource.repositoryid}") do
+ if ( platform?("fedora") && node["platform_version"].to_i >= 22 ) ||
+ ( platform_family?("rhel") && node["platform_version"].to_i >= 8 )
+ block { Chef::Provider::Package::Dnf::PythonHelper.instance.restart }
+ else
+ block { Chef::Provider::Package::Yum::YumCache.instance.reload }
+ end
action :nothing
end
end
action :delete do
- declare_resource(:file, "/etc/yum.repos.d/#{new_resource.repositoryid}.repo") do
- action :delete
- notifies :run, "execute[yum clean all #{new_resource.repositoryid}]", :immediately
- notifies :create, "ruby_block[yum-cache-reload-#{new_resource.repositoryid}]", :immediately
- end
-
+ # clean the repo cache first
declare_resource(:execute, "yum clean all #{new_resource.repositoryid}") do
command "yum clean all --disablerepo=* --enablerepo=#{new_resource.repositoryid}"
- only_if "yum repolist | grep -P '^#{new_resource.repositoryid}([ \t]|$)'"
- action :nothing
+ only_if "yum repolist all | grep -P '^#{new_resource.repositoryid}([ \t]|$)'"
end
- declare_resource(:ruby_block, "yum-cache-reload-#{new_resource.repositoryid}") do
- block { Chef::Provider::Package::Yum::YumCache.instance.reload }
+ declare_resource(:file, ::File.join(new_resource.reposdir, "#{new_resource.repositoryid}.repo")) do
+ action :delete
+ notifies :create, "ruby_block[package-cache-reload-#{new_resource.repositoryid}]", :immediately
+ end
+
+ declare_resource(:ruby_block, "package-cache-reload-#{new_resource.repositoryid}") do
+ if ( platform?("fedora") && node["platform_version"].to_i >= 22 ) ||
+ ( platform_family?("rhel") && node["platform_version"].to_i >= 8 )
+ block { Chef::Provider::Package::Dnf::PythonHelper.instance.restart }
+ else
+ block { Chef::Provider::Package::Yum::YumCache.instance.reload }
+ end
action :nothing
end
end
@@ -101,8 +104,13 @@ class Chef
only_if { new_resource.enabled }
end
- declare_resource(:ruby_block, "yum-cache-reload-#{new_resource.repositoryid}") do
- block { Chef::Provider::Package::Yum::YumCache.instance.reload }
+ declare_resource(:ruby_block, "package-cache-reload-#{new_resource.repositoryid}") do
+ if ( platform?("fedora") && node["platform_version"].to_i >= 22 ) ||
+ ( platform_family?("rhel") && node["platform_version"].to_i >= 8 )
+ block { Chef::Provider::Package::Dnf::PythonHelper.instance.restart }
+ else
+ block { Chef::Provider::Package::Yum::YumCache.instance.reload }
+ end
action :run
end
end
diff --git a/lib/chef/provider/zypper_repository.rb b/lib/chef/provider/zypper_repository.rb
new file mode 100644
index 0000000000..53dae74948
--- /dev/null
+++ b/lib/chef/provider/zypper_repository.rb
@@ -0,0 +1,188 @@
+#
+# Author:: Tim Smith (<tsmith@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../resource"
+require_relative "../dsl/declare_resource"
+require_relative "noop"
+require "shellwords" unless defined?(Shellwords)
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Provider
+ class ZypperRepository < Chef::Provider
+ provides :zypper_repository, platform_family: "suse"
+
+ def load_current_resource; end
+
+ action :create do
+ if new_resource.gpgautoimportkeys
+ install_gpg_key(new_resource.gpgkey)
+ else
+ logger.trace("'gpgautoimportkeys' property is set to false. Skipping key import.")
+ end
+
+ declare_resource(:template, "/etc/zypp/repos.d/#{escaped_repo_name}.repo") do
+ if template_available?(new_resource.source)
+ source new_resource.source
+ else
+ source ::File.expand_path("support/zypper_repo.erb", __dir__)
+ local true
+ end
+ sensitive new_resource.sensitive
+ variables(config: new_resource)
+ mode new_resource.mode
+ notifies :refresh, new_resource, :immediately if new_resource.refresh_cache
+ end
+ end
+
+ action :delete do
+ declare_resource(:execute, "zypper --quiet --non-interactive removerepo #{escaped_repo_name}") do
+ only_if "zypper --quiet lr #{escaped_repo_name}"
+ end
+ end
+
+ action :refresh do
+ declare_resource(:execute, "zypper --quiet --non-interactive refresh --force #{escaped_repo_name}") do
+ only_if "zypper --quiet lr #{escaped_repo_name}"
+ end
+ end
+
+ alias_method :action_add, :action_create
+ alias_method :action_remove, :action_delete
+
+ # zypper repos are allowed to have spaces in the names
+ # @return [String] escaped repo string
+ def escaped_repo_name
+ Shellwords.escape(new_resource.repo_name)
+ end
+
+ # return the specified cookbook name or the cookbook containing the
+ # resource.
+ #
+ # @return [String] name of the cookbook
+ def cookbook_name
+ new_resource.cookbook || new_resource.cookbook_name
+ end
+
+ # determine if a template file is available in the current run
+ # @param [String] path the path to the template file
+ #
+ # @return [Boolean] template file exists or doesn't
+ def template_available?(path)
+ !path.nil? && run_context.has_template_in_cookbook?(cookbook_name, path)
+ end
+
+ # determine if a cookbook file is available in the run
+ # @param [String] fn the path to the template file
+ #
+ # @return [Boolean] cookbook file exists or doesn't
+ def has_cookbook_file?(fn)
+ run_context.has_cookbook_file_in_cookbook?(cookbook_name, fn)
+ end
+
+ # Given the provided key URI determine what kind of chef resource we need
+ # to fetch the key
+ # @param [String] uri the uri of the gpg key (local path or http URL)
+ #
+ # @raise [Chef::Exceptions::FileNotFound] Key isn't remote or found in the current run
+ #
+ # @return [Symbol] :remote_file or :cookbook_file
+ def key_type(uri)
+ if uri.start_with?("http")
+ logger.trace("Will use :remote_file resource to cache the gpg key locally")
+ :remote_file
+ elsif has_cookbook_file?(uri)
+ logger.trace("Will use :cookbook_file resource to cache the gpg key locally")
+ :cookbook_file
+ else
+ raise Chef::Exceptions::FileNotFound, "Cannot determine location of gpgkey. Must start with 'http' or be a file managed by #{ChefUtils::Dist::Infra::PRODUCT}."
+ end
+ end
+
+ # the version of gpg installed on the system
+ #
+ # @return [Gem::Version] the version of GPG
+ def gpg_version
+ so = shell_out!("gpg --version")
+ # matches 2.0 and 2.2 versions from SLES 12 and 15: https://rubular.com/r/e6D0WfGK6SXvUp
+ version = /gpg \(GnuPG\)\s*(.*)/.match(so.stdout)[1]
+ logger.trace("GPG package version is #{version}")
+ Gem::Version.new(version)
+ end
+
+ # is the provided key already installed
+ # @param [String] key_path the path to the key on the local filesystem
+ #
+ # @return [boolean] is the key already known by rpm
+ def key_installed?(key_path)
+ so = shell_out("/bin/rpm -qa gpg-pubkey*")
+ # expected output & match: http://rubular.com/r/RdF7EcXEtb
+ status = /gpg-pubkey-#{short_key_id(key_path)}/.match(so.stdout)
+ logger.trace("GPG key at #{key_path} is known by rpm? #{status ? "true" : "false"}")
+ status
+ end
+
+ # extract the gpg key's short key id from a local file. Learning moment: This 8 hex value ID
+ # is sometimes incorrectly called the fingerprint. The fingerprint is the full length value
+ # and googling for that will just result in sad times.
+ #
+ # @param [String] key_path the path to the key on the local filesystem
+ #
+ # @return [String] the short key id of the key
+ def short_key_id(key_path)
+ if gpg_version >= Gem::Version.new("2.2") # SLES 15+
+ so = shell_out!("gpg --import-options import-show --dry-run --import --with-colons #{key_path}")
+ # expected output and match: https://rubular.com/r/uXWJo3yfkli1qA
+ short_key_id = /fpr:*\h*(\h{8}):/.match(so.stdout)[1].downcase
+ else # SLES 12 and earlier
+ so = shell_out!("gpg --with-fingerprint #{key_path}")
+ # expected output and match: http://rubular.com/r/BpfMjxySQM
+ short_key_id = %r{pub\s*\S*/(\S*)}.match(so.stdout)[1].downcase
+ end
+ logger.trace("GPG short key ID of key at #{key_path} is #{short_key_id}")
+ short_key_id
+ end
+
+ # install the provided gpg key
+ # @param [String] uri the uri of the local or remote gpg key
+ def install_gpg_key(uri)
+ unless uri
+ logger.trace("'gpgkey' property not provided or set to nil. Skipping key import.")
+ return
+ end
+
+ cached_keyfile = ::File.join(Chef::Config[:file_cache_path], uri.split("/")[-1])
+
+ declare_resource(key_type(new_resource.gpgkey), cached_keyfile) do
+ source uri
+ mode "0644"
+ sensitive new_resource.sensitive
+ action :create
+ end
+
+ declare_resource(:execute, "import gpg key from #{new_resource.gpgkey}") do
+ command "/bin/rpm --import #{cached_keyfile}"
+ not_if { key_installed?(cached_keyfile) }
+ action :run
+ end
+ end
+ end
+ end
+end
+
+Chef::Provider::Noop.provides :zypper_repository
diff --git a/lib/chef/provider_resolver.rb b/lib/chef/provider_resolver.rb
index 1df473abbd..94727a1043 100644
--- a/lib/chef/provider_resolver.rb
+++ b/lib/chef/provider_resolver.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require "chef/exceptions"
-require "chef/platform/priority_map"
+require_relative "exceptions"
+require_relative "platform/priority_map"
class Chef
#
@@ -58,8 +58,9 @@ class Chef
def resolve
maybe_explicit_provider(resource) ||
+ maybe_custom_resource(resource) ||
maybe_dynamic_provider_resolution(resource, action) ||
- maybe_chef_platform_lookup(resource)
+ raise(Chef::Exceptions::ProviderNotFound, "Cannot find a provider for #{resource} on #{node["platform"]} version #{node["platform_version"]}")
end
# Does NOT call provides? on the resource (it is assumed this is being
@@ -90,9 +91,11 @@ class Chef
@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."
+ # We always require a provider to be able to call define_resource_requirements on. In the why-run case we need
+ # a provider to say "assuming /etc/init.d/whatever would have been installed" and in the non-why-run case we
+ # need to make a best guess at "cannot find /etc/init.d/whatever". We are essentially defining a "default" provider
+ # for the platform, which is the best we can do, but which might give misleading errors, but we cannot read minds.
+ Chef::Log.trace "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
@@ -103,32 +106,31 @@ class Chef
end
end
+ # if its a custom resource, just grab the action class
+ def maybe_custom_resource(resource)
+ resource.class.action_class if resource.class.custom_resource?
+ end
+
# if resource.provider is set, just return one of those objects
def maybe_explicit_provider(resource)
- return nil unless resource.provider
resource.provider
end
# try dynamically finding a provider based on querying the providers to see what they support
def maybe_dynamic_provider_resolution(resource, action)
- Chef::Log.debug "Providers for generic #{resource.resource_name} resource enabled on node include: #{enabled_handlers}"
+ Chef::Log.trace "Providers for generic #{resource.resource_name} resource enabled on node include: #{enabled_handlers}"
handler = prioritized_handlers.first
if handler
- Chef::Log.debug "Provider for action #{action} on resource #{resource} is #{handler}"
+ Chef::Log.trace "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}"
+ Chef::Log.trace "Dynamic provider resolver FAILED to resolve a provider for action #{action} on resource #{resource}"
end
handler
end
- # try the old static lookup of providers by platform
- def maybe_chef_platform_lookup(resource)
- Chef::Platform.find_provider_for_node(node, resource)
- end
-
def priority_map
Chef.provider_priority_map
end
@@ -140,31 +142,5 @@ class Chef
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 affa5ca2c1..7652d60896 100644
--- a/lib/chef/providers.rb
+++ b/lib/chef/providers.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,133 +16,123 @@
# limitations under the License.
#
-require "chef/provider/apt_update"
-require "chef/provider/apt_repository"
-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/launchd"
-require "chef/provider/link"
-require "chef/provider/log"
-require "chef/provider/ohai"
-require "chef/provider/mdadm"
-require "chef/provider/mount"
-require "chef/provider/noop"
-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/systemd_unit"
-require "chef/provider/template"
-require "chef/provider/user"
-require "chef/provider/whyrun_safe_ruby_block"
-require "chef/provider/yum_repository"
+require_relative "provider/batch"
+require_relative "provider/cookbook_file"
+require_relative "provider/cron"
+require_relative "provider/cron/solaris"
+require_relative "provider/cron/aix"
+require_relative "provider/directory"
+require_relative "provider/dsc_script"
+require_relative "provider/dsc_resource"
+require_relative "provider/execute"
+require_relative "provider/file"
+require_relative "provider/git"
+require_relative "provider/group"
+require_relative "provider/http_request"
+require_relative "provider/ifconfig"
+require_relative "provider/launchd"
+require_relative "provider/link"
+require_relative "provider/mount"
+require_relative "provider/noop"
+require_relative "provider/package"
+require_relative "provider/powershell_script"
+require_relative "provider/remote_directory"
+require_relative "provider/remote_file"
+require_relative "provider/route"
+require_relative "provider/ruby_block"
+require_relative "provider/script"
+require_relative "provider/service"
+require_relative "provider/subversion"
+require_relative "provider/systemd_unit"
+require_relative "provider/template"
+require_relative "provider/user"
+require_relative "provider/whyrun_safe_ruby_block"
+require_relative "provider/yum_repository"
+require_relative "provider/zypper_repository"
-require "chef/provider/env/windows"
+require_relative "provider/package/apt"
+require_relative "provider/package/chocolatey"
+require_relative "provider/package/dpkg"
+require_relative "provider/package/dnf"
+require_relative "provider/package/freebsd/port"
+require_relative "provider/package/freebsd/pkgng"
+require_relative "provider/package/homebrew"
+require_relative "provider/package/ips"
+require_relative "provider/package/macports"
+require_relative "provider/package/openbsd"
+require_relative "provider/package/pacman"
+require_relative "provider/package/portage"
+require_relative "provider/package/paludis"
+require_relative "provider/package/rpm"
+require_relative "provider/package/rubygems"
+require_relative "provider/package/yum"
+require_relative "provider/package/zypper"
+require_relative "provider/package/solaris"
+require_relative "provider/package/smartos"
+require_relative "provider/package/bff"
+require_relative "provider/package/cab"
+require_relative "provider/package/powershell"
+require_relative "provider/package/msu"
+require_relative "provider/package/snap"
-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_relative "provider/service/arch"
+require_relative "provider/service/freebsd"
+require_relative "provider/service/gentoo"
+require_relative "provider/service/init"
+require_relative "provider/service/invokercd"
+require_relative "provider/service/debian"
+require_relative "provider/service/openbsd"
+require_relative "provider/service/redhat"
+require_relative "provider/service/insserv"
+require_relative "provider/service/simple"
+require_relative "provider/service/systemd"
+require_relative "provider/service/upstart"
+require_relative "provider/service/windows"
+require_relative "provider/service/solaris"
+require_relative "provider/service/macosx"
+require_relative "provider/service/aixinit"
+require_relative "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_relative "provider/user/aix"
+require_relative "provider/user/dscl"
+require_relative "provider/user/linux"
+require_relative "provider/user/mac"
+require_relative "provider/user/pw"
+require_relative "provider/user/solaris"
+require_relative "provider/user/windows"
-require "chef/provider/user/aix"
-require "chef/provider/user/dscl"
-require "chef/provider/user/linux"
-require "chef/provider/user/pw"
-require "chef/provider/user/solaris"
-require "chef/provider/user/useradd"
-require "chef/provider/user/windows"
+require_relative "provider/group/aix"
+require_relative "provider/group/dscl"
+require_relative "provider/group/gpasswd"
+require_relative "provider/group/groupadd"
+require_relative "provider/group/groupmod"
+require_relative "provider/group/pw"
+require_relative "provider/group/solaris"
+require_relative "provider/group/suse"
+require_relative "provider/group/usermod"
+require_relative "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_relative "provider/mount/mount"
+require_relative "provider/mount/aix"
+require_relative "provider/mount/solaris"
+require_relative "provider/mount/windows"
+require_relative "provider/mount/linux"
-require "chef/provider/mount/mount"
-require "chef/provider/mount/aix"
-require "chef/provider/mount/solaris"
-require "chef/provider/mount/windows"
+require_relative "provider/remote_file/ftp"
+require_relative "provider/remote_file/sftp"
+require_relative "provider/remote_file/http"
+require_relative "provider/remote_file/local_file"
+require_relative "provider/remote_file/network_file"
+require_relative "provider/remote_file/fetcher"
-require "chef/provider/deploy/revision"
-require "chef/provider/deploy/timestamped"
+require_relative "provider/lwrp_base"
+require_relative "provider/registry_key"
-require "chef/provider/remote_file/ftp"
-require "chef/provider/remote_file/sftp"
-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_relative "provider/file/content"
+require_relative "provider/remote_file/content"
+require_relative "provider/cookbook_file/content"
+require_relative "provider/template/content"
-require "chef/provider/lwrp_base"
-require "chef/provider/registry_key"
-
-require "chef/provider/file/content"
-require "chef/provider/remote_file/content"
-require "chef/provider/cookbook_file/content"
-require "chef/provider/template/content"
-
-require "chef/provider/ifconfig/redhat"
-require "chef/provider/ifconfig/debian"
-require "chef/provider/ifconfig/aix"
+require_relative "provider/ifconfig/redhat"
+require_relative "provider/ifconfig/debian"
+require_relative "provider/ifconfig/aix"
diff --git a/lib/chef/pwsh.rb b/lib/chef/pwsh.rb
new file mode 100644
index 0000000000..3d067eb0d6
--- /dev/null
+++ b/lib/chef/pwsh.rb
@@ -0,0 +1,71 @@
+#
+# Author:: Matt Wrock (<mwrock@chef.io>)
+# Copyright:: Copyright (c) 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 Pwsh < Chef::PowerShell
+
+ # Run a command under pwsh (powershell core) via FFI
+ # This implementation requires the managed dll, native wrapper and a
+ # published, self contained dotnet core directory tree to exist in the
+ # bindir directory.
+ #
+ # @param script [String] script to run
+ # @return [Object] output
+ def initialize(script)
+ @dll = Pwsh.dll
+ super
+ end
+
+ protected
+
+ def exec(script)
+ # Note that we need to override the location of the shared dotnet core library
+ # location. With most .net core applications, you can simply publish them as a
+ # "self-contained" application allowing consumers of the application to run them
+ # and use its own stand alone version of the .net core runtime. However because
+ # this is simply a dll and not an exe, it will look for the runtime in the shared
+ # .net core installation folder. By setting DOTNET_MULTILEVEL_LOOKUP to 0 we can
+ # override that folder's location with DOTNET_ROOT. To avoid the possibility of
+ # interfering with other .net core processes that might rely on the common shared
+ # location, we revert these variables after the script completes.
+ original_dml = ENV["DOTNET_MULTILEVEL_LOOKUP"]
+ original_dotnet_root = ENV["DOTNET_ROOT"]
+ original_dotnet_root_x86 = ENV["DOTNET_ROOT(x86)"]
+
+ ENV["DOTNET_MULTILEVEL_LOOKUP"] = "0"
+ ENV["DOTNET_ROOT"] = RbConfig::CONFIG["bindir"]
+ ENV["DOTNET_ROOT(x86)"] = RbConfig::CONFIG["bindir"]
+
+ super
+ ensure
+ ENV["DOTNET_MULTILEVEL_LOOKUP"] = original_dml
+ ENV["DOTNET_ROOT"] = original_dotnet_root
+ ENV["DOTNET_ROOT(x86)"] = original_dotnet_root_x86
+ end
+
+ def self.dll
+ # This Powershell DLL source lives here: https://github.com/chef/chef-powershell-shim
+ # Every merge into that repo triggers a Habitat build and promotion. Running
+ # the rake :update_chef_exec_dll task in this (chef/chef) repo will pull down
+ # the built packages and copy the binaries to distro/ruby_bin_folder. Bundle install
+ # ensures that the correct architecture binaries are installed into the path.
+ # Also note that the version of pwsh is determined by which assemblies the dll was
+ # built with. To update powershell, those dependencies must be bumped.
+ @dll ||= Dir.glob("#{RbConfig::CONFIG["bindir"]}/**/Chef.PowerShell.Wrapper.Core.dll").last
+ end
+ end
+end
diff --git a/lib/chef/recipe.rb b/lib/chef/recipe.rb
index 77d82f83ab..972edf9649 100644
--- a/lib/chef/recipe.rb
+++ b/lib/chef/recipe.rb
@@ -1,7 +1,7 @@
#--
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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 "chef/dsl/recipe"
-require "chef/mixin/from_file"
-require "chef/mixin/deprecation"
+autoload :YAML, "yaml"
+require_relative "dsl/recipe"
+require_relative "mixin/from_file"
+require_relative "mixin/deprecation"
class Chef
# == Chef::Recipe
@@ -47,6 +48,7 @@ class Chef
[ $1.to_sym, $2 ]
when /^::(.+)/
raise "current_cookbook is nil, cannot resolve #{recipe_name}" if current_cookbook.nil?
+
[ current_cookbook.to_sym, $1 ]
else
[ recipe_name.to_sym, "default" ]
@@ -58,7 +60,7 @@ class Chef
@recipe_name = recipe_name
@run_context = run_context
# TODO: 5/19/2010 cw/tim: determine whether this can be removed
- @params = Hash.new
+ @params = {}
end
# Used in DSL mixins
@@ -66,32 +68,11 @@ class Chef
run_context.node
end
- # Used by the DSL to look up resources when executing in the context of a
- # recipe.
- def resources(*args)
- run_context.resource_collection.find(*args)
- end
-
# This was moved to Chef::Node#tag, redirecting here for compatibility
def tag(*tags)
run_context.node.tag(*tags)
end
- # Returns true if the node is tagged with *all* of the supplied +tags+.
- #
- # === Parameters
- # tags<Array>:: A list of tags
- #
- # === Returns
- # true<TrueClass>:: If all the parameters are present
- # false<FalseClass>:: If any of the parameters are missing
- def tagged?(*tags)
- tags.each do |tag|
- return false unless run_context.node.tags.include?(tag)
- end
- true
- end
-
# Removes the list of tags from the node.
#
# === Parameters
@@ -104,5 +85,48 @@ class Chef
run_context.node.tags.delete(tag)
end
end
+
+ def from_yaml_file(filename)
+ self.source_file = filename
+ if File.file?(filename) && File.readable?(filename)
+ yaml_contents = IO.read(filename)
+ if ::YAML.load_stream(yaml_contents).length > 1
+ raise ArgumentError, "YAML recipe '#{filename}' contains multiple documents, only one is supported"
+ end
+
+ from_yaml(yaml_contents)
+ else
+ raise IOError, "Cannot open or read file '#{filename}'!"
+ end
+ end
+
+ def from_yaml(string)
+ res = ::YAML.safe_load(string)
+ unless res.is_a?(Hash) && res.key?("resources")
+ raise ArgumentError, "YAML recipe '#{source_file}' must contain a top-level 'resources' hash (YAML sequence), i.e. 'resources:'"
+ end
+
+ from_hash(res)
+ end
+
+ def from_hash(hash)
+ hash["resources"].each do |rhash|
+ type = rhash.delete("type").to_sym
+ name = rhash.delete("name")
+ res = declare_resource(type, name)
+ rhash.each do |key, value|
+ # FIXME?: we probably need a way to instance_exec a string that contains block code against the property?
+ res.send(key, value)
+ end
+ end
+ end
+
+ def to_s
+ "cookbook: #{cookbook_name || "(none)"}, recipe: #{recipe_name || "(none)"} "
+ end
+
+ def inspect
+ to_s
+ end
end
end
diff --git a/lib/chef/request_id.rb b/lib/chef/request_id.rb
index 33e54d9edc..9aad3532a5 100644
--- a/lib/chef/request_id.rb
+++ b/lib/chef/request_id.rb
@@ -1,5 +1,5 @@
# Author:: Prajakta Purohit (<prajakta@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,8 +15,8 @@
# limitations under the License.
#
-require "securerandom"
-require "singleton"
+require "securerandom" unless defined?(SecureRandom)
+require "singleton" unless defined?(Singleton)
class Chef
class RequestID
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index d11fa1c80c..e572f0667d 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -18,30 +18,30 @@
# limitations under the License.
#
-require "chef/exceptions"
-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/dsl/universal"
+require_relative "exceptions"
+require_relative "dsl/reboot_pending"
+require_relative "dsl/resources"
+require_relative "dsl/declare_resource"
+require_relative "json_compat"
+require_relative "mixin/convert_to_class_name"
+require_relative "guard_interpreter/resource_guard_interpreter"
+require_relative "resource/conditional"
+require_relative "resource/conditional_action_not_nothing"
+require_relative "resource/action_class"
+require_relative "resource_collection"
+require_relative "node_map"
+require_relative "node"
+require_relative "platform"
+require_relative "resource/resource_notification"
+require_relative "provider_resolver"
+require_relative "resource_resolver"
+require_relative "provider"
+autoload :Set, "set"
+
+require_relative "mixin/deprecation"
+require_relative "mixin/properties"
+require_relative "mixin/provides"
+require_relative "dsl/universal"
class Chef
class Resource
@@ -50,12 +50,12 @@ class Chef
# Generic User DSL (not resource-specific)
#
- include Chef::DSL::DataQuery
- include Chef::DSL::RegistryHelper
+ include Chef::DSL::DeclareResource
include Chef::DSL::RebootPending
extend Chef::Mixin::Provides
include Chef::DSL::Universal
+ extend Chef::DSL::Universal
# Bring in `property` and `property_type`
include Chef::Mixin::Properties
@@ -83,7 +83,7 @@ class Chef
# @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
+ property :name, String, coerce: proc { |v| v.is_a?(Array) ? v.join(", ") : v.to_s }, desired_state: false, required: true
#
# The node the current Chef run is using.
@@ -97,26 +97,6 @@ class Chef
end
#
- # Find existing resources by searching the list of existing resources. Possible
- # forms are:
- #
- # find(:file => "foobar")
- # find(:file => [ "foobar", "baz" ])
- # find("file[foobar]", "file[baz]")
- # find("file[foobar,baz]")
- #
- # Calls `run_context.resource_collection.find(*args)`
- #
- # @return the matching resource, or an Array of matching resources.
- #
- # @raise ArgumentError if you feed it bad lookup information
- # @raise RuntimeError if it can't find the resources you are looking for.
- #
- def resources(*args)
- run_context.resource_collection.find(*args)
- end
-
- #
# Resource User Interface (for users)
#
@@ -130,21 +110,25 @@ class Chef
def initialize(name, run_context = nil)
name(name) unless name.nil?
@run_context = run_context
- @noop = nil
+
+ @logger = if run_context
+ run_context.logger.with_child({ name: name, resource: resource_name })
+ else
+ Chef::Log.with_child({ name: name, resource: resource_name })
+ end
+
@before = nil
- @params = Hash.new
+ @params = {}
@provider = nil
@allowed_actions = self.class.allowed_actions.to_a
@action = self.class.default_action
@updated = false
@updated_by_last_action = false
- @supports = {}
- @ignore_failure = false
- @retries = 0
- @retry_delay = 2
@not_if = []
@only_if = []
@source_line = nil
+ @deprecated = false
+ @skip_docs = false
# We would like to raise an error when the user gives us a guard
# interpreter and a ruby_block to the guard. In order to achieve this
# we need to understand when the user overrides the default guard
@@ -153,7 +137,7 @@ class Chef
@guard_interpreter = nil
@default_guard_interpreter = :default
@elapsed_time = 0
- @sensitive = false
+ @executed_by_runner = false
end
#
@@ -177,10 +161,29 @@ class Chef
end
end
- # Alias for normal assigment syntax.
+ # Alias for normal assignment syntax.
alias_method :action=, :action
#
+ # Force a delayed notification into this resource's run_context.
+ #
+ # This should most likely be paired with action :nothing
+ #
+ # @param arg [Array[Symbol], Symbol] A list of actions (e.g. `:create`)
+ #
+ def delayed_action(arg)
+ arg = Array(arg).map(&:to_sym)
+ arg.map do |action|
+ validate(
+ { action: action },
+ { action: { kind_of: Symbol, equal_to: allowed_actions } }
+ )
+ # the resource effectively sends a delayed notification to itself
+ run_context.add_delayed_action(Notification.new(self, action, self, run_context.unified_mode))
+ end
+ end
+
+ #
# Sets up a notification that will run a particular action on another resource
# if and when *this* resource is updated by an action.
#
@@ -408,7 +411,6 @@ class Chef
@not_if
end
- #
# The number of times to retry this resource if it fails by throwing an
# exception while running an action. Default: 0
#
@@ -418,40 +420,44 @@ class Chef
# @param arg [Integer] The number of retries.
# @return [Integer] The number of retries.
#
- def retries(arg = nil)
- set_or_return(:retries, arg, kind_of: Integer)
- end
- attr_writer :retries
+ property :retries, Integer, default: 0, desired_state: false
- #
# The number of seconds to wait between retries. Default: 2.
#
# @param arg [Integer] The number of seconds to wait between retries.
# @return [Integer] The number of seconds to wait between retries.
#
- def retry_delay(arg = nil)
- set_or_return(:retry_delay, arg, kind_of: Integer)
- end
- attr_writer :retry_delay
+ property :retry_delay, Integer, default: 2, desired_state: false
- #
# Whether to treat this resource's data as sensitive. If set, no resource
# data will be displayed in log output.
#
# @param arg [Boolean] Whether this resource is sensitive or not.
# @return [Boolean] Whether this resource is sensitive or not.
#
- def sensitive(arg = nil)
- set_or_return(:sensitive, arg, :kind_of => [ TrueClass, FalseClass ])
- end
- attr_writer :sensitive
+ property :sensitive, [ TrueClass, FalseClass ], default: false, desired_state: false
- # ??? TODO unreferenced. Delete?
- attr_reader :not_if_args
- # ??? TODO unreferenced. Delete?
- attr_reader :only_if_args
+ # If this is set the resource will be set to run at compile time and the converge time
+ # action will be set to :nothing.
+ #
+ # @param arg [Boolean] Whether or not to force this resource to run at compile time.
+ # @return [Boolean] Whether or not to force this resource to run at compile time.
+ #
+ property :compile_time, [TrueClass, FalseClass],
+ description: "Determines whether or not the resource is executed during the compile time phase.",
+ default: false, desired_state: false
+ # Set a umask to be used for the duration of converging the resource.
+ # Defaults to `nil`, which means to use the system umask.
#
+ # @param arg [String] The umask to apply while converging the resource.
+ # @return [Boolean] The umask to apply while converging the resource.
+ #
+ property :umask, String,
+ desired_state: false,
+ introduced: "16.2",
+ description: "Set a umask to be used for the duration of converging the resource. Defaults to `nil`, which means to use the system umask. Unsupported on Windows because Windows lacks a direct equivalent to UNIX's umask."
+
# The time it took (in seconds) to run the most recently-run action. Not
# cumulative across actions. This is set to 0 as soon as a new action starts
# running, and set to the elapsed time at the end of the action.
@@ -461,7 +467,10 @@ class Chef
#
attr_reader :elapsed_time
+ # @return [Boolean] If the resource was executed by the runner
#
+ attr_accessor :executed_by_runner
+
# The guard interpreter that will be used to process `only_if` and `not_if`
# statements. If left unset, the #default_guard_interpreter will be used.
#
@@ -479,7 +488,7 @@ class Chef
if arg.nil?
@guard_interpreter || @default_guard_interpreter
else
- set_or_return(:guard_interpreter, arg, :kind_of => Symbol)
+ set_or_return(:guard_interpreter, arg, kind_of: Symbol)
end
end
@@ -496,7 +505,7 @@ class Chef
state = {}
state_properties = self.class.state_properties
state_properties.each do |property|
- if property.identity? || property.is_set?(self)
+ if property.is_set?(self)
state[property.name] = property.sensitive? ? "*sensitive value suppressed*" : send(property.name)
end
end
@@ -504,15 +513,6 @@ class Chef
end
#
- # Since there are collisions with LWRP parameters named 'state' this
- # method is not used by the resource_reporter and is most likely unused.
- # It certainly cannot be relied upon and cannot be fixed.
- #
- # @deprecated
- #
- alias_method :state, :state_for_resource_reporter
-
- #
# The value of the identity of this resource.
#
# - If there are no identity properties on the resource, `name` is returned.
@@ -528,29 +528,23 @@ class Chef
result[property.name] = send(property.name)
end
return result.values.first if identity_properties.size == 1
+
result
end
#
# Whether to ignore failures. If set to `true`, and this resource when an
# action is run, the resource will be marked as failed but no exception will
- # be thrown (and no error will be output). Defaults to `false`.
+ # be thrown (and no error will be output). Defaults to `false`. If set to
+ # `:quiet` or `'quiet'`, the normal error trace will be suppressed.
#
# TODO ignore_failure and retries seem to be mutually exclusive; I doubt
# that was intended.
#
- # @param arg [Boolean] Whether to ignore failures.
+ # @param arg [Boolean, String, Symbol] Whether to ignore failures.
# @return Whether this resource will ignore failures.
#
- def ignore_failure(arg = nil)
- set_or_return(:ignore_failure, arg, kind_of: [ TrueClass, FalseClass ])
- end
- attr_writer :ignore_failure
-
- #
- # Equivalent to #ignore_failure.
- #
- alias :epic_fail :ignore_failure
+ property :ignore_failure, [ true, false, :quiet, "quiet" ], default: false, desired_state: false
#
# Make this resource into an exact (shallow) copy of the other resource.
@@ -560,7 +554,7 @@ class Chef
def load_from(resource)
resource.instance_variables.each do |iv|
unless iv == :@source_line || iv == :@action || iv == :@not_if || iv == :@only_if
- self.instance_variable_set(iv, resource.instance_variable_get(iv))
+ instance_variable_set(iv, resource.instance_variable_get(iv))
end
end
end
@@ -584,9 +578,9 @@ class Chef
resolve_notification_references
validate_action(action)
- if Chef::Config[:verbose_logging] || Chef::Log.level == :debug
+ if Chef::Config[:verbose_logging] || logger.level == :debug
# This can be noisy
- Chef::Log.info("Processing #{self} action #{action} (#{defined_at})")
+ logger.info("Processing #{self} action #{action} (#{defined_at})")
end
# ensure that we don't leave @updated_by_last_action set to true
@@ -600,15 +594,18 @@ class Chef
begin
return if should_skip?(action)
- provider_for_action(action).run_action
- rescue Exception => e
+
+ with_umask do
+ provider_for_action(action).run_action
+ end
+ rescue StandardError => e
if ignore_failure
- Chef::Log.error("#{custom_exception_message(e)}; ignore_failure is set, continuing")
+ logger.error("#{custom_exception_message(e)}; ignore_failure is set, continuing")
events.resource_failed(self, action, e)
elsif remaining_retries > 0
events.resource_failed_retriable(self, action, remaining_retries, e)
remaining_retries -= 1
- Chef::Log.info("Retrying execution of #{self}, #{remaining_retries} attempt(s) left")
+ logger.info("Retrying execution of #{self}, #{remaining_retries} attempt#{"s" if remaining_retries > 1} left")
sleep retry_delay
retry
else
@@ -624,12 +621,20 @@ class Chef
events.resource_completed(self)
end
+ def with_umask
+ old_value = ::File.umask(umask.oct) if umask
+ yield
+ ensure
+ ::File.umask(old_value) if umask
+ 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
@@ -648,23 +653,41 @@ class Chef
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 << "#{resource_name}(\"#{name}\") do\n"
+
+ all_props = {}
+ self.class.state_properties.map do |p|
+
+ all_props[p.name.to_s] = p.sensitive? ? '"*sensitive value suppressed*"' : value_to_text(p.get(self))
+ rescue Chef::Exceptions::ValidationFailed
+ # This space left intentionally blank, the property was probably required or had an invalid default.
+
+ end
+
+ ivars = instance_variables.map(&:to_sym) - HIDDEN_IVARS
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
- text << " #{ivar.to_s.sub(/^@/, '')} #{value_string}\n"
+ iv = ivar.to_s.sub(/^@/, "")
+ if all_props.key?(iv)
+ text << " #{iv} #{all_props[iv]}\n"
+ elsif (value = instance_variable_get(ivar)) && !(value.respond_to?(:empty?) && value.empty?)
+ text << " #{iv} #{value_to_text(value)}\n"
end
end
+
[@not_if, @only_if].flatten.each do |conditional|
text << " #{conditional.to_text}\n"
end
text << "end\n"
end
+ def value_to_text(value)
+ value.respond_to?(:to_text) ? value.to_text : value.inspect
+ end
+
def inspect
- ivars = instance_variables.map { |ivar| ivar.to_sym } - FORBIDDEN_IVARS
+ ivars = instance_variables.map(&:to_sym) - FORBIDDEN_IVARS
ivars.inject("<#{self}") do |str, ivar|
str << " #{ivar}: #{instance_variable_get(ivar).inspect}"
end << ">"
@@ -674,8 +697,8 @@ class Chef
# is loaded. activesupport will call as_json and skip over to_json. This ensure
# json is encoded as expected
def as_json(*a)
- safe_ivars = instance_variables.map { |ivar| ivar.to_sym } - FORBIDDEN_IVARS
- instance_vars = Hash.new
+ safe_ivars = instance_variables.map(&:to_sym) - FORBIDDEN_IVARS
+ instance_vars = {}
safe_ivars.each do |iv|
instance_vars[iv.to_s.sub(/^@/, "")] = instance_variable_get(iv)
end
@@ -691,29 +714,40 @@ class Chef
Chef::JSONCompat.to_json(results, *a)
end
- def to_hash
+ def to_h
# 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
+ safe_ivars = instance_variables.map(&:to_sym) - FORBIDDEN_IVARS
safe_ivars.each do |iv|
key = iv.to_s.sub(/^@/, "").to_sym
- next if result.has_key?(key)
+ next if result.key?(key)
+
result[key] = instance_variable_get(iv)
end
result
end
- def self.json_create(o)
- resource = self.new(o["instance_vars"]["@name"])
+ alias_method :to_hash, :to_h
+
+ def self.from_hash(o)
+ resource = new(o["instance_vars"]["@name"])
o["instance_vars"].each do |k, v|
resource.instance_variable_set("@#{k}".to_sym, v)
end
resource
end
+ def self.json_create(o)
+ from_hash(o)
+ end
+
+ def self.from_json(j)
+ from_hash(Chef::JSONCompat.parse(j))
+ end
+
#
# Resource Definition Interface (for resource developers)
#
@@ -738,13 +772,12 @@ class Chef
# @see Chef::Resource.action_class
#
def provider(arg = nil)
- klass = if arg.kind_of?(String) || arg.kind_of?(Symbol)
+ klass = if arg.is_a?(String) || arg.is_a?(Symbol)
lookup_provider_constant(arg)
else
arg
end
- set_or_return(:provider, klass, kind_of: [ Class ]) ||
- self.class.action_class
+ set_or_return(:provider, klass, kind_of: [ Class ])
end
def provider=(arg)
@@ -772,7 +805,7 @@ class Chef
# @return [Array<Symbol>] All property names with desired state.
#
def self.state_attrs(*names)
- state_properties(*names).map { |property| property.name }
+ state_properties(*names).map(&:name)
end
#
@@ -802,8 +835,9 @@ class Chef
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(", ")})."
+ raise Chef::Exceptions::MultipleIdentityError, "identity_property cannot be called on an object with more than one identity property (#{result.map(&:name).join(", ")})."
end
+
result.first
end
@@ -823,7 +857,8 @@ class Chef
#
def self.identity_attr(name = nil)
property = identity_property(name)
- return nil if !property
+ return nil unless property
+
property.name
end
@@ -849,7 +884,8 @@ class Chef
# @return [Array<Symbol>] The list of actions this Resource is allowed to
# have.
#
- attr_accessor :allowed_actions
+ attr_writer :allowed_actions
+
def allowed_actions(value = NOT_PASSED)
if value != NOT_PASSED
self.allowed_actions = value
@@ -901,19 +937,6 @@ class Chef
end
#
- # Set whether this class was updated during an action.
- #
- # @deprecated Multiple actions are supported by resources. Please call {}#updated_by_last_action} instead.
- #
- def updated=(true_or_false)
- Chef::Log.warn("Chef::Resource#updated=(true|false) is deprecated. Please call #updated_by_last_action(true|false) instead.")
- Chef::Log.warn("Called from:")
- caller[0..3].each { |line| Chef::Log.warn(line) }
- updated_by_last_action(true_or_false)
- @updated = true_or_false
- end
-
- #
# The display name of this resource type, for printing purposes.
#
# Will be used to print out the resource in messages, e.g. resource_name[name]
@@ -925,30 +948,7 @@ class Chef
end
#
- # Sets a list of capabilities of the real resource. For example, `:remount`
- # (for filesystems) and `:restart` (for services).
- #
- # TODO Calling resource.supports({}) will not set this to empty; it will do
- # a get instead. That's wrong.
- #
- # @param args Hash{Symbol=>Boolean} If non-empty, sets the capabilities of
- # this resource. Default: {}
- # @return Hash{Symbol=>Boolean} An array of things this resource supports.
- #
- def supports(args = {})
- if args.any?
- @supports = args
- else
- @supports
- end
- end
-
- def supports=(args)
- supports(args)
- end
-
- #
- # A hook called after a resource is created. Meant to be overriden by
+ # A hook called after a resource is created. Meant to be overridden by
# subclasses.
#
def after_created
@@ -956,30 +956,9 @@ 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).
+ # Call `resource_name nil` to remove the resource name
#
# @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.
@@ -989,20 +968,10 @@ class Chef
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
+ @resource_name = name.to_sym rescue nil
end
+
+ @resource_name = nil unless defined?(@resource_name)
@resource_name
end
@@ -1010,40 +979,14 @@ class Chef
resource_name(name)
end
+ # If the resource's action should run in separated compile/converge mode.
#
- # 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`.
- #
- # @param arg [Module] The module containing providers for this resource
- # @return [Module] The module containing providers for this resource
- #
- # @example
- # class MyResource < Chef::Resource
- # provider_base Chef::Provider::Deploy
- # # ...other stuff
- # end
- #
- # @deprecated Use `provides` on the provider, or `provider` on the resource, instead.
- #
- def self.provider_base(arg = nil)
- if arg
- Chef.log_deprecation("Resource.provider_base is deprecated and will be removed in Chef 13. Use provides on the provider, or provider on the resource, instead.")
- end
- @provider_base ||= arg || Chef::Provider
+ # @param flag [Boolean] value to set unified_mode to
+ # @return [Boolean] unified_mode value
+ def self.unified_mode(flag = nil)
+ @unified_mode = Chef::Config[:resource_unified_mode_default] if !defined?(@unified_mode) || @unified_mode.nil?
+ @unified_mode = flag unless flag.nil?
+ !!@unified_mode
end
#
@@ -1070,7 +1013,7 @@ class Chef
#
# The action that will be run if no other action is specified.
#
- # Setting default_action will automatially add the action to
+ # Setting default_action will automatically add the action to
# allowed_actions, if it isn't already there.
#
# Defaults to [:nothing].
@@ -1086,7 +1029,7 @@ class Chef
self.allowed_actions |= @default_action
end
- if @default_action
+ if defined?(@default_action) && @default_action
@default_action
elsif superclass.respond_to?(:default_action)
superclass.default_action
@@ -1099,7 +1042,6 @@ class Chef
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
@@ -1137,7 +1079,6 @@ class Chef
default_action action if Array(default_action) == [:nothing]
end
- #
# Define a method to load up this resource's properties with the current
# actual values.
#
@@ -1148,7 +1089,6 @@ class Chef
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`.
#
@@ -1158,7 +1098,6 @@ class Chef
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.
@@ -1171,66 +1110,95 @@ class Chef
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`.
+ # The action class is a `Chef::Provider` which is created at Resource
+ # class evaluation time when the Custom Resource is being constructed.
#
- # 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`.
+ # This happens the first time the ruby parser hits an `action` or an
+ # `action_class` method, the presence of either indicates that this is
+ # going to be a Chef-12.5 custom resource. If we never see one of these
+ # directives then we are constructing an old-style Resource+Provider or
+ # LWRP or whatever.
#
# 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 ||= declare_action_class
+ @action_class.class_eval(&block) if block
@action_class
end
+ # Returns true or false based on if the resource is a custom resource. The
+ # top-level Chef::Resource is not a chef resource. This value is inherited.
+ #
+ # @return [Boolean] if the resource is a custom_resource
+ def self.custom_resource?
+ false
+ end
+
+ # This sets the resource to being a custom resource, and does so in a way
+ # that automatically inherits to all subclasses via defining a method on
+ # the class (class variables and class instance variables don't have the
+ # correct semantics here, this is a poor man's activesupport class_attribute)
#
+ # @api private
+ def self.is_custom_resource!
+ define_singleton_method :custom_resource? do
+ true
+ end
+ 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
+ def self.declare_action_class
+ @action_class ||=
+ begin
+ is_custom_resource!
+ base_provider =
+ if superclass.custom_resource?
+ superclass.action_class
+ else
+ ActionClass
+ end
+
+ resource_class = self
+ Class.new(base_provider) do
+ self.resource_class = resource_class
+ end
+ end
+ end
+
+ # Set or return if this resource is in preview mode.
+ #
+ # This only has value in the resource_inspector to mark a resource as being new-to-chef-core.
+ # Its meaning is probably more equivalent to "experimental" in that the API might change even
+ # in minor versions due to bugfixing and is NOT considered "stable" yet.
+ #
+ # @param value [nil, Boolean] If nil, get the current value. If not nil, set
+ # the value of the flag.
+ # @return [Boolean]
+ def self.preview_resource(value = nil)
+ @preview_resource = false unless defined?(@preview_resource)
+ @preview_resource = value unless value.nil?
+ @preview_resource
end
#
# Internal Resource Interface (for Chef)
#
- FORBIDDEN_IVARS = [:@run_context, :@not_if, :@only_if, :@enclosing_provider]
- HIDDEN_IVARS = [:@allowed_actions, :@resource_name, :@source_line, :@run_context, :@name, :@not_if, :@only_if, :@elapsed_time, :@enclosing_provider]
+ # FORBIDDEN_IVARS do not show up when the resource is converted to JSON (ie. hidden from data_collector and sending to the chef server via #to_json/to_h/as_json/inspect)
+ FORBIDDEN_IVARS = %i{@run_context @logger @not_if @only_if @enclosing_provider @description @introduced @examples @validation_message @deprecated @default_description @skip_docs @executed_by_runner}.freeze
+ # HIDDEN_IVARS do not show up when the resource is displayed to the user as text (ie. in the error inspector output via #to_text)
+ HIDDEN_IVARS = %i{@allowed_actions @resource_name @source_line @run_context @logger @name @not_if @only_if @elapsed_time @enclosing_provider @description @introduced @examples @validation_message @deprecated @default_description @skip_docs @executed_by_runner}.freeze
include Chef::Mixin::ConvertToClassName
extend Chef::Mixin::ConvertToClassName
@@ -1242,18 +1210,27 @@ class Chef
# @return [Chef::RunContext] The run context for this Resource. This is
# where the context for the current Chef run is stored, including the node
# and the resource collection.
+ #
attr_accessor :run_context
+ # @return [Mixlib::Log::Child] The logger for this resources. This is a child
+ # of the run context's logger, if one exists.
+ #
+ attr_reader :logger
+
# @return [String] The cookbook this resource was declared in.
+ #
attr_accessor :cookbook_name
# @return [String] The recipe this resource was declared in.
+ #
attr_accessor :recipe_name
# @return [Chef::Provider] The provider this resource was declared in (if
# it was declared in an LWRP). When you call methods that do not exist
# on this Resource, Chef will try to call the method on the provider
# as well before giving up.
+ #
attr_accessor :enclosing_provider
# @return [String] The source line where this resource was declared.
@@ -1261,6 +1238,7 @@ class Chef
# of these formats:
# /some/path/to/file.rb:80:in `wombat_tears'
# C:/some/path/to/file.rb:80 in 1`wombat_tears'
+ #
attr_accessor :source_line
# @return [String] The actual name that was used to create this resource.
@@ -1269,37 +1247,40 @@ class Chef
# user will expect to see the thing they wrote, not the type that was
# returned. May be `nil`, in which case callers should read #resource_name.
# See #declared_key.
+ #
attr_accessor :declared_type
- #
# Iterates over all immediate and delayed notifications, calling
# resolve_resource_reference on each in turn, causing them to
# resolve lazy/forward references.
- def resolve_notification_references
+ #
+ def resolve_notification_references(always_raise = false)
run_context.before_notifications(self).each do |n|
- n.resolve_resource_reference(run_context.resource_collection)
+ n.resolve_resource_reference(run_context.resource_collection, true)
end
+
run_context.immediate_notifications(self).each do |n|
- n.resolve_resource_reference(run_context.resource_collection)
+ n.resolve_resource_reference(run_context.resource_collection, always_raise)
end
+
run_context.delayed_notifications(self).each do |n|
- n.resolve_resource_reference(run_context.resource_collection)
+ n.resolve_resource_reference(run_context.resource_collection, always_raise)
end
end
# Helper for #notifies
def notifies_before(action, resource_spec)
- run_context.notifies_before(Notification.new(resource_spec, action, self))
+ run_context.notifies_before(Notification.new(resource_spec, action, self, run_context.unified_mode))
end
# Helper for #notifies
def notifies_immediately(action, resource_spec)
- run_context.notifies_immediately(Notification.new(resource_spec, action, self))
+ run_context.notifies_immediately(Notification.new(resource_spec, action, self, run_context.unified_mode))
end
# Helper for #notifies
def notifies_delayed(action, resource_spec)
- run_context.notifies_delayed(Notification.new(resource_spec, action, self))
+ run_context.notifies_delayed(Notification.new(resource_spec, action, self, run_context.unified_mode))
end
class << self
@@ -1323,18 +1304,12 @@ class Chef
# life as well.
@@sorted_descendants = nil
def self.sorted_descendants
- @@sorted_descendants ||= descendants.sort_by { |x| x.to_s }
+ @@sorted_descendants ||= descendants.sort_by(&: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
@@ -1348,7 +1323,25 @@ class Chef
end
end
+ # This API can be used for backcompat to do:
+ #
+ # chef_version_for_provides "< 14.0" if defined?(:chef_version_for_provides)
+ #
+ # For core chef versions that do not support chef_version: in provides lines.
+ #
+ # Since resource_name calls provides the generally correct way of doing this is
+ # to do `chef_version_for_provides` first, then `resource_name` and then
+ # any additional options `provides` lines.
+ #
+ # Once we no longer care about supporting chef < 14.4 then we can deprecate
+ # this API.
#
+ # @param arg [String] version constraint to match against (e.g. "> 14")
+ #
+ def self.chef_version_for_provides(constraint)
+ @chef_version_for_provides = constraint
+ end
+
# Mark this resource as providing particular DSL.
#
# Resources have an automatic DSL based on their resource_name, equivalent to
@@ -1359,13 +1352,17 @@ class Chef
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
+ # quell warnings
+ @chef_version_for_provides = nil unless defined?(@chef_version_for_provides)
+
+ # deliberately do not go through the accessor here
+ @resource_name = name if resource_name.nil?
+
+ if @chef_version_for_provides && !options.include?(:chef_version)
+ options[:chef_version] = @chef_version_for_provides
end
- result = Chef.resource_handler_map.set(name, self, options, &block)
+ result = Chef.resource_handler_map.set(name, self, **options, &block)
Chef::DSL::Resources.add_resource_dsl(name)
result
end
@@ -1384,6 +1381,7 @@ class Chef
# the declared key we want to fall back on the old to_s key.
def declared_key
return to_s if declared_type.nil?
+
"#{declared_type}[#{@name}]"
end
@@ -1429,7 +1427,63 @@ class Chef
end
end
+ def self.description(description = "NOT_PASSED")
+ if description != "NOT_PASSED"
+ @description = description
+ end
+ @description
+ end
+
+ def self.introduced(introduced = "NOT_PASSED")
+ if introduced != "NOT_PASSED"
+ @introduced = introduced
+ end
+ @introduced
+ end
+
+ def self.examples(examples = "NOT_PASSED")
+ if examples != "NOT_PASSED"
+ @examples = examples
+ end
+ @examples
+ end
+
+ def self.deprecated(deprecated = "NOT_PASSED")
+ if deprecated != "NOT_PASSED"
+ @deprecated = true
+ @deprecated_message = deprecated
+ end
+ @deprecated
+ end
+
+ def self.skip_docs(skip_docs = "NOT_PASSED")
+ if skip_docs != "NOT_PASSED"
+ @skip_docs = skip_docs
+ end
+ @skip_docs
+ end
+
+ def self.default_description(default_description = "NOT_PASSED")
+ if default_description != "NOT_PASSED"
+ @default_description = default_description
+ end
+ @default_description
+ end
+
+ # Use a partial code fragment. This can be used for code sharing between multiple resources.
+ #
+ # Do not wrap the code fragment in a class or module. It also does not support the use of super
+ # to subclass any methods defined in the fragment, the methods will just be overwritten.
+ #
+ # @param partial [String] the code fragment to eval against the class
#
+ def self.use(partial)
+ dirname = ::File.dirname(partial)
+ basename = ::File.basename(partial, ".rb")
+ basename = basename[1..] if basename.start_with?("_")
+ class_eval IO.read(::File.expand_path("#{dirname}/_#{basename}.rb", ::File.dirname(caller_locations.first.absolute_path)))
+ end
+
# The cookbook in which this Resource was defined (if any).
#
# @return Chef::CookbookVersion The cookbook in which this Resource was defined.
@@ -1455,25 +1509,6 @@ class Chef
provider
end
- # ??? TODO Seems unused. Delete?
- def noop(tf = nil)
- if !tf.nil?
- raise ArgumentError, "noop must be true or false!" unless tf == true || tf == false
- @noop = tf
- end
- @noop
- end
-
- # TODO Seems unused. Delete?
- def is(*args)
- if args.size == 1
- args.first
- else
- return *args
- end
- end
-
- #
# Preface an exception message with generic Resource information.
#
# @param e [StandardError] An exception with `e.message`
@@ -1508,7 +1543,7 @@ class Chef
false
else
events.resource_skipped(self, action, conditional)
- Chef::Log.debug("Skipping #{self} due to #{conditional.description}")
+ logger.debug("Skipping #{self} due to #{conditional.description}")
true
end
end
@@ -1525,12 +1560,19 @@ class Chef
def self.resource_for_node(short_name, node)
klass = Chef::ResourceResolver.resolve(short_name, node: node)
raise Chef::Exceptions::NoSuchResourceType.new(short_name, node) if klass.nil?
+
klass
end
- #
# Returns the class with the given resource_name.
#
+ # NOTE: Chef::Resource.resource_matching_short_name(:package) returns
+ # Chef::Resource::Package, while on rhel the API call
+ # Chef::Resource.resource_for_node(:package, node) will return
+ # Chef::Resource::YumPackage -- which is probably what you really
+ # want. This API should most likely be removed or changed to call
+ # resource_for_node.
+ #
# ==== Parameters
# short_name<Symbol>:: short_name of the resource (ie :directory)
#
@@ -1538,54 +1580,32 @@ class Chef
# <Chef::Resource>:: returns the proper Chef::Resource class
#
def self.resource_matching_short_name(short_name)
- Chef::ResourceResolver.resolve(short_name, canonical: true)
+ Chef::ResourceResolver.resolve(short_name)
end
# @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
- if e.to_s =~ /#{Regexp.escape(self.class.provider_base.to_s)}/
- raise ArgumentError, "No provider found to match '#{name}'"
- else
- raise e
- end
- end
+ # XXX: "name" is probably a poor choice of name here, ideally this would be nil, but we need to
+ # fix resources so that nil or empty names work (also solving the apt_update "doesn't matter one bit"
+ # problem). WARNING: this string is not a public API and should not be referenced (e.g. in provides blocks)
+ # and may change at any time. If you've found this comment you're also probably very lost and should maybe
+ # consider using `declare_resource :whatever` instead of trying to set `provider :whatever` on a resource, or in some
+ # other way reconsider what you're trying to do, since you're likely trying to force a bad design that we
+ # can't/won't support.
+ self.class.resource_for_node(name, node).new("name", run_context).provider_for_action(action).class
end
- module DeprecatedLWRPClass
-
- # @api private
- def 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)
- Chef::Resource.deprecated_constants[class_name.to_sym] = resource_class
- end
- end
-
- def deprecated_constants
- raise "Deprecated constants should be called only on Chef::Resource" unless self == Chef::Resource
- @deprecated_constants ||= {}
- end
- end
-
- 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
+ # This is used to suppress the "(up to date)" message in the doc formatter
+ # for the log resource (where it is nonsensical).
+ #
+ # This is not exactly a private API, but its doubtful there exist many other sane
+ # use cases for this.
+ #
+ def suppress_up_to_date_messages?
+ false
end
- extend DeprecatedLWRPClass
end
end
# Requiring things at the bottom breaks cycles
-require "chef/chef_class"
+require_relative "chef_class"
diff --git a/lib/chef/resource/action_class.rb b/lib/chef/resource/action_class.rb
index 98b4d87ef1..a1b0c4e73e 100644
--- a/lib/chef/resource/action_class.rb
+++ b/lib/chef/resource/action_class.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io)
-# Copyright:: Copyright 2015-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,78 +16,76 @@
# limitations under the License.
#
-require "chef/exceptions"
-require "chef/dsl/recipe"
+require_relative "../provider"
+require_relative "../exceptions"
+require_relative "../dsl/recipe"
class Chef
class Resource
- module ActionClass
+ class ActionClass < Chef::Provider
include Chef::DSL::Recipe
def to_s
"#{new_resource || "<no resource>"} action #{action ? action.inspect : "<no action>"}"
end
- def whyrun_supported?
- true
- end
-
- #
- # If load_current_value! is defined on the resource, use that.
- #
- def load_current_resource
+ def return_load_current_value
+ resource = nil
if new_resource.respond_to?(:load_current_value!)
- # dup the resource and then reset desired-state properties.
- current_resource = new_resource.dup
+ resource = new_resource.class.new(new_resource.name, new_resource.run_context)
- # 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)
+ # copy the non-desired state, the identity properties and name property to the new resource
+ # (the desired state values must be loaded by load_current_value)
+ resource.class.properties.each_value do |property|
+ if !property.desired_state? || property.identity? || property.name_property?
+ property.set(resource, new_resource.send(property.name)) if new_resource.class.properties[property.name].is_set?(new_resource)
end
end
- # Call the actual load_current_value! method. If it raises
- # CurrentValueDoesNotExist, set current_resource to `nil`.
+ # we support optionally passing the new_resource as an arg to load_current_value and
+ # load_current_value can raise in order to clear the 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)
+ if resource.method(:load_current_value!).arity > 0
+ resource.load_current_value!(new_resource)
else
- current_resource.load_current_value!
+ resource.load_current_value!
end
rescue Chef::Exceptions::CurrentValueDoesNotExist
- current_resource = nil
+ resource = nil
end
end
+ resource
+ end
- @current_resource = current_resource
+ # build the before state (current_resource)
+ def load_current_resource
+ @current_resource = return_load_current_value
end
- def self.included(other)
- other.extend(ClassMethods)
- other.use_inline_resources
- other.include_resource_dsl true
+ # build the after state (after_resource)
+ def load_after_resource
+ @after_resource = return_load_current_value
end
- module ClassMethods
+ def self.include_resource_dsl?
+ true
+ end
+
+ class << self
#
# The Chef::Resource class this ActionClass was declared against.
#
# @return [Class] The Chef::Resource class this ActionClass was declared against.
#
attr_accessor :resource_class
+ end
- def to_s
- "#{resource_class} action provider"
- end
+ def self.to_s
+ "#{resource_class} action provider"
+ end
- def inspect
- to_s
- end
+ def self.inspect
+ to_s
end
end
end
diff --git a/lib/chef/resource/alternatives.rb b/lib/chef/resource/alternatives.rb
new file mode 100644
index 0000000000..fe5af6b7b6
--- /dev/null
+++ b/lib/chef/resource/alternatives.rb
@@ -0,0 +1,210 @@
+#
+# Copyright:: Copyright (c) Chef Software Inc.
+# Copyright:: 2016-2020, Virender Khatri
+#
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../resource"
+
+class Chef
+ class Resource
+ class Alternatives < Chef::Resource
+ unified_mode true
+
+ provides(:alternatives) { true }
+
+ description "The alternatives resource allows for configuration of command alternatives in Linux using the alternatives or update-alternatives packages."
+ introduced "16.0"
+ examples <<~DOC
+ **Install an alternative**:
+
+ ```ruby
+ alternatives 'python install 2' do
+ link_name 'python'
+ path '/usr/bin/python2.7'
+ priority 100
+ action :install
+ end
+ ```
+
+ **Set an alternative**:
+
+ ```ruby
+ alternatives 'python set version 3' do
+ link_name 'python'
+ path '/usr/bin/python3'
+ action :set
+ end
+ ```
+
+ **Set the automatic alternative state**:
+
+ ```ruby
+ alternatives 'python auto' do
+ link_name 'python'
+ action :auto
+ end
+ ```
+
+ **Refresh an alternative**:
+
+ ```ruby
+ alternatives 'python refresh' do
+ link_name 'python'
+ action :refresh
+ end
+ ```
+
+ **Remove an alternative**:
+
+ ```ruby
+ alternatives 'python remove' do
+ link_name 'python'
+ path '/usr/bin/python3'
+ action :remove
+ end
+ ```
+ DOC
+
+ property :link_name, String,
+ name_property: true,
+ description: "The name of the link to create. This will be the command you type on the command line such as `ruby` or `gcc`."
+
+ property :link, String,
+ default: lazy { |n| "/usr/bin/#{n.link_name}" },
+ default_description: "/usr/bin/LINK_NAME",
+ description: "The path to the alternatives link."
+
+ property :path, String,
+ description: "The absolute path to the original application binary such as `/usr/bin/ruby27`."
+
+ property :priority, [String, Integer],
+ coerce: proc { |n| n.to_i },
+ description: "The priority of the alternative."
+
+ def define_resource_requirements
+ requirements.assert(:install) do |a|
+ a.assertion do
+ !new_resource.priority.nil?
+ end
+
+ a.failure_message("Could not set alternatives for #{new_resource.link_name}, you must provide the :priority property")
+ end
+
+ requirements.assert(:install, :set, :remove) do |a|
+ a.assertion do
+ !new_resource.path.nil?
+ end
+
+ a.failure_message("Could not set alternatives for #{new_resource.link_name}, you must provide the :path property")
+ end
+
+ requirements.assert(:install, :set, :remove) do |a|
+ a.assertion do
+ ::File.exist?(new_resource.path)
+ end
+
+ a.whyrun("Assuming file #{new_resource.path} already exists or was created already")
+ a.failure_message("Could not set alternatives for #{new_resource.link_name}, missing #{new_resource.path}")
+ end
+ end
+
+ action :install do
+ if path_priority != new_resource.priority
+ converge_by("adding alternative #{new_resource.link} #{new_resource.link_name} #{new_resource.path} #{new_resource.priority}") do
+ output = shell_out(alternatives_cmd, "--install", new_resource.link, new_resource.link_name, new_resource.path, new_resource.priority)
+ unless output.exitstatus == 0
+ raise "failed to add alternative #{new_resource.link} #{new_resource.link_name} #{new_resource.path} #{new_resource.priority}"
+ end
+ end
+ end
+ end
+
+ action :set do
+ if current_path != new_resource.path
+ converge_by("setting alternative #{new_resource.link_name} #{new_resource.path}") do
+ output = shell_out(alternatives_cmd, "--set", new_resource.link_name, new_resource.path)
+ unless output.exitstatus == 0
+ raise "failed to set alternative #{new_resource.link_name} #{new_resource.path} \n #{output.stdout.strip}"
+ end
+ end
+ end
+ end
+
+ action :remove do
+ if path_exists?
+ converge_by("removing alternative #{new_resource.link_name} #{new_resource.path}") do
+ shell_out(alternatives_cmd, "--remove", new_resource.link_name, new_resource.path)
+ end
+ end
+ end
+
+ action :auto do
+ converge_by("setting auto alternative #{new_resource.link_name}") do
+ shell_out(alternatives_cmd, "--auto", new_resource.link_name)
+ end
+ end
+
+ action :refresh do
+ converge_by("refreshing alternative #{new_resource.link_name}") do
+ shell_out(alternatives_cmd, "--refresh", new_resource.link_name)
+ end
+ end
+
+ action_class do
+ #
+ # @return [String] The appropriate alternatives command based on the platform
+ #
+ def alternatives_cmd
+ if debian?
+ "update-alternatives"
+ else
+ "alternatives"
+ end
+ end
+
+ #
+ # @return [Integer] The current path priority for the link_name alternative
+ #
+ def path_priority
+ # https://rubular.com/r/IcUlEU0mSNaMm3
+ escaped_path = Regexp.new(Regexp.escape("#{new_resource.path} - priority ") + "(.*)")
+ match = shell_out(alternatives_cmd, "--display", new_resource.link_name).stdout.match(escaped_path)
+
+ match.nil? ? nil : match[1].to_i
+ end
+
+ #
+ # @return [String] The current path for the link_name alternative
+ #
+ def current_path
+ # https://rubular.com/r/ylsuvzUtquRPqc
+ match = shell_out(alternatives_cmd, "--display", new_resource.link_name).stdout.match(/link currently points to (.*)/)
+ match[1]
+ end
+
+ #
+ # @return [Boolean] does the path exist for the link_name alternative
+ #
+ def path_exists?
+ # https://rubular.com/r/ogvDdq8h2IKRff
+ escaped_path = Regexp.new(Regexp.escape("#{new_resource.path} - priority"))
+ shell_out(alternatives_cmd, "--display", new_resource.link_name).stdout.match?(escaped_path)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/apt_package.rb b/lib/chef/resource/apt_package.rb
index 069fefcb2b..0a31f89af3 100644
--- a/lib/chef/resource/apt_package.rb
+++ b/lib/chef/resource/apt_package.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,16 +16,63 @@
# limitations under the License.
#
-require "chef/resource/package"
-require "chef/provider/package/apt"
+require_relative "package"
class Chef
class Resource
class AptPackage < Chef::Resource::Package
- resource_name :apt_package
- provides :package, os: "linux", platform_family: [ "debian" ]
+ unified_mode true
- property :default_release, String, desired_state: false
+ provides :apt_package, target_mode: true
+ provides :package, platform_family: "debian", target_mode: true
+ examples <<~DOC
+ **Install a package using package manager**:
+
+ ```ruby
+ apt_package 'name of package' do
+ action :install
+ end
+ ```
+
+ **Install a package without specifying the default action**:
+
+ ```ruby
+ apt_package 'name of package'
+ ```
+
+ **Install multiple packages at once**:
+
+ ```ruby
+ apt_package %(package1 package2 package3)
+ ```
+
+ **Install without using recommend packages as a dependency**:
+
+ ```ruby
+ package 'apache2' do
+ options '--no-install-recommends'
+ end
+ ```
+ DOC
+
+ description "Use the **apt_package** resource to manage packages on Debian and Ubuntu platforms."
+
+ property :default_release, String,
+ description: "The default release. For example: `stable`.",
+ desired_state: false
+
+ property :overwrite_config_files, [TrueClass, FalseClass],
+ introduced: "14.0",
+ description: "Overwrite existing configuration files with those supplied by the package, if prompted by APT.",
+ default: false
+
+ property :response_file, String,
+ description: "The direct path to the file used to pre-seed a package.",
+ desired_state: false
+
+ property :response_file_variables, Hash,
+ description: "A Hash of response file variables in the form of {'VARIABLE' => 'VALUE'}.",
+ default: lazy { {} }, desired_state: false
end
end
diff --git a/lib/chef/resource/apt_preference.rb b/lib/chef/resource/apt_preference.rb
new file mode 100644
index 0000000000..fd987466ea
--- /dev/null
+++ b/lib/chef/resource/apt_preference.rb
@@ -0,0 +1,148 @@
+#
+# Author:: Tim Smith (<tsmith@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class AptPreference < Chef::Resource
+ unified_mode true
+
+ provides(:apt_preference) { true }
+
+ description "Use the **apt_preference** resource to create APT [preference files](https://wiki.debian.org/AptPreferences). Preference files are used to control which package versions and sources are prioritized during installation."
+ introduced "13.3"
+ examples <<~DOC
+ **Pin libmysqlclient16 to a version 5.1.49-3**:
+
+ ```ruby
+ apt_preference 'libmysqlclient16' do
+ pin 'version 5.1.49-3'
+ pin_priority '700'
+ end
+ ```
+
+ Note: The `pin_priority` of `700` ensures that this version will be preferred over any other available versions.
+
+ **Unpin a libmysqlclient16**:
+
+ ```ruby
+ apt_preference 'libmysqlclient16' do
+ action :remove
+ end
+ ```
+
+ **Pin all packages to prefer the packages.dotdeb.org repository**:
+
+ ```ruby
+ apt_preference 'dotdeb' do
+ glob '*'
+ pin 'origin packages.dotdeb.org'
+ pin_priority '700'
+ end
+ ```
+ DOC
+
+ property :package_name, String,
+ name_property: true,
+ description: "An optional property to set the package name if it differs from the resource block's name.",
+ regex: [/^([a-z]|[A-Z]|[0-9]|_|-|\.|\*|\+)+$/],
+ validation_message: "The provided package name is not valid. Package names can only contain alphanumeric characters as well as _, -, +, or *!"
+
+ property :glob, String,
+ description: "Pin by a `glob()` expression or with a regular expression surrounded by `/`."
+
+ property :pin, String,
+ description: "The package version or repository to pin.",
+ required: [:add]
+
+ property :pin_priority, [String, Integer],
+ description: "Sets the Pin-Priority for a package. See <https://wiki.debian.org/AptPreferences> for more details.",
+ required: [:add]
+
+ default_action :add
+ allowed_actions :add, :remove
+
+ APT_PREFERENCE_DIR = "/etc/apt/preferences.d".freeze
+
+ action_class do
+ # Build preferences.d file contents
+ def build_pref(package_name, pin, pin_priority)
+ "Package: #{package_name}\nPin: #{pin}\nPin-Priority: #{pin_priority}\n"
+ end
+
+ def safe_name(name)
+ name.tr(".", "_").gsub("*", "wildcard")
+ end
+ end
+
+ action :add do
+ return unless debian?
+
+ preference = build_pref(
+ new_resource.glob || new_resource.package_name,
+ new_resource.pin,
+ new_resource.pin_priority
+ )
+
+ directory APT_PREFERENCE_DIR do
+ mode "0755"
+ action :create
+ end
+
+ sanitized_prefname = safe_name(new_resource.package_name)
+
+ # cleanup any existing pref files w/o the sanitized name (created by old apt cookbook)
+ if (sanitized_prefname != new_resource.package_name) && ::File.exist?("#{APT_PREFERENCE_DIR}/#{new_resource.package_name}.pref")
+ logger.warn "Replacing legacy #{new_resource.package_name}.pref with #{sanitized_prefname}.pref in #{APT_PREFERENCE_DIR}"
+ file "#{APT_PREFERENCE_DIR}/#{new_resource.package_name}.pref" do
+ action :delete
+ end
+ end
+
+ # cleanup any existing pref files without the .pref extension (created by old apt cookbook)
+ if ::File.exist?("#{APT_PREFERENCE_DIR}/#{new_resource.package_name}")
+ logger.warn "Replacing legacy #{new_resource.package_name} with #{sanitized_prefname}.pref in #{APT_PREFERENCE_DIR}"
+ file "#{APT_PREFERENCE_DIR}/#{new_resource.package_name}" do
+ action :delete
+ end
+ end
+
+ file "#{APT_PREFERENCE_DIR}/#{sanitized_prefname}.pref" do
+ mode "0644"
+ content preference
+ action :create
+ end
+ end
+
+ action :remove do
+ return unless debian?
+
+ sanitized_prefname = safe_name(new_resource.package_name)
+
+ if ::File.exist?("#{APT_PREFERENCE_DIR}/#{sanitized_prefname}.pref")
+ logger.info "Un-pinning #{sanitized_prefname} from #{APT_PREFERENCE_DIR}"
+ file "#{APT_PREFERENCE_DIR}/#{sanitized_prefname}.pref" do
+ action :delete
+ end
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/resource/apt_repository.rb b/lib/chef/resource/apt_repository.rb
index 8b87371824..da8ca78413 100644
--- a/lib/chef/resource/apt_repository.rb
+++ b/lib/chef/resource/apt_repository.rb
@@ -1,6 +1,6 @@
#
# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright (c) 2016 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,32 +16,471 @@
# limitations under the License.
#
-require "chef/resource"
+require_relative "../resource"
+require_relative "../http/simple"
+require "tmpdir" unless defined?(Dir.mktmpdir)
+module Addressable
+ autoload :URI, "addressable/uri"
+end
class Chef
class Resource
class AptRepository < Chef::Resource
- resource_name :apt_repository
- provides :apt_repository
-
- property :repo_name, String, name_property: true
- property :uri, String
- property :distribution, [ String, nil, false ], default: lazy { node["lsb"]["codename"] }, nillable: true, coerce: proc { |x| x ? x : nil }
- property :components, Array, default: []
- property :arch, [String, nil, false], default: nil, nillable: true, coerce: proc { |x| x ? x : nil }
- property :trusted, [TrueClass, FalseClass], default: false
- # whether or not to add the repository as a source repo, too
- property :deb_src, [TrueClass, FalseClass], default: false
- property :keyserver, [String, nil, false], default: "keyserver.ubuntu.com", nillable: true, coerce: proc { |x| x ? x : nil }
- property :key, [String, nil, false], default: nil, nillable: true, coerce: proc { |x| x ? x : nil }
- property :key_proxy, [String, nil, false], default: nil, nillable: true, coerce: proc { |x| x ? x : nil }
-
- property :cookbook, [String, nil, false], default: nil, desired_state: false, nillable: true, coerce: proc { |x| x ? x : nil }
- property :cache_rebuild, [TrueClass, FalseClass], default: true, desired_state: false
- property :sensitive, [TrueClass, FalseClass], default: false, desired_state: false
+ unified_mode true
+
+ provides(:apt_repository) { true }
+
+ description "Use the **apt_repository** resource to specify additional APT repositories. Adding a new repository will update the APT package cache immediately."
+ introduced "12.9"
+
+ examples <<~DOC
+ **Add repository with basic settings**:
+
+ ```ruby
+ apt_repository 'nginx' do
+ uri 'http://nginx.org/packages/ubuntu/'
+ components ['nginx']
+ end
+ ```
+
+ **Enable Ubuntu multiverse repositories**:
+
+ ```ruby
+ apt_repository 'security-ubuntu-multiverse' do
+ uri 'http://security.ubuntu.com/ubuntu'
+ distribution 'xenial-security'
+ components ['multiverse']
+ deb_src true
+ end
+ ```
+
+ **Add the Nginx PPA, autodetect the key and repository url**:
+
+ ```ruby
+ apt_repository 'nginx-php' do
+ uri 'ppa:nginx/stable'
+ end
+ ```
+
+ **Add the JuJu PPA, grab the key from the Ubuntu keyserver, and add source repo**:
+
+ ```ruby
+ apt_repository 'juju' do
+ uri 'ppa:juju/stable'
+ components ['main']
+ distribution 'xenial'
+ key 'C8068B11'
+ action :add
+ deb_src true
+ end
+ ```
+
+ **Add repository that requires multiple keys to authenticate packages**:
+
+ ```ruby
+ apt_repository 'rundeck' do
+ uri 'https://dl.bintray.com/rundeck/rundeck-deb'
+ distribution '/'
+ key ['379CE192D401AB61', 'http://rundeck.org/keys/BUILD-GPG-KEY-Rundeck.org.key']
+ keyserver 'keyserver.ubuntu.com'
+ action :add
+ end
+ ```
+
+ **Add the Cloudera Repo of CDH4 packages for Ubuntu 16.04 on AMD64**:
+
+ ```ruby
+ apt_repository 'cloudera' do
+ uri 'http://archive.cloudera.com/cdh4/ubuntu/xenial/amd64/cdh'
+ arch 'amd64'
+ distribution 'xenial-cdh4'
+ components ['contrib']
+ key 'http://archive.cloudera.com/debian/archive.key'
+ end
+ ```
+
+ **Remove a repository from the list**:
+
+ ```ruby
+ apt_repository 'zenoss' do
+ action :remove
+ end
+ ```
+ DOC
+
+ # There's a pile of [ String, nil, FalseClass ] types in these properties.
+ # This goes back to Chef 12 where String didn't default to nil and we had to do
+ # it ourself, which required allowing that type as well. We've cleaned up the
+ # defaults, but since we allowed users to pass nil here we need to continue
+ # to allow that so don't refactor this however tempting it is
+ property :repo_name, String,
+ regex: [%r{^[^/]+$}],
+ description: "An optional property to set the repository name if it differs from the resource block's name. The value of this setting must not contain spaces.",
+ validation_message: "repo_name property cannot contain a forward slash '/'",
+ introduced: "14.1", name_property: true
+
+ property :uri, String,
+ description: "The base of the Debian distribution."
+
+ property :distribution, [ String, nil, FalseClass ],
+ description: "Usually a distribution's codename, such as `xenial`, `bionic`, or `focal`.",
+ default: lazy { node["lsb"]["codename"] }, default_description: "The LSB codename of the node such as 'focal'."
+
+ property :components, Array,
+ description: "Package groupings, such as 'main' and 'stable'.",
+ default: lazy { [] }, default_description: "`main` if using a PPA repository."
+
+ property :arch, [String, nil, FalseClass],
+ description: "Constrain packages to a particular CPU architecture such as `i386` or `amd64`."
+
+ property :trusted, [TrueClass, FalseClass],
+ description: "Determines whether you should treat all packages from this repository as authenticated regardless of signature.",
+ default: false
+
+ property :deb_src, [TrueClass, FalseClass],
+ description: "Determines whether or not to add the repository as a source repo as well.",
+ default: false
+
+ property :keyserver, [String, nil, FalseClass],
+ description: "The GPG keyserver where the key for the repo should be retrieved.",
+ default: "keyserver.ubuntu.com"
+
+ property :key, [String, Array, nil, FalseClass],
+ description: "If a keyserver is provided, this is assumed to be the fingerprint; otherwise it can be either the URI of GPG key for the repo, or a cookbook_file.",
+ default: lazy { [] }, coerce: proc { |x| x ? Array(x) : x }
+
+ property :key_proxy, [String, nil, FalseClass],
+ description: "If set, a specified proxy is passed to GPG via `http-proxy=`."
+
+ property :cookbook, [String, nil, FalseClass],
+ description: "If key should be a cookbook_file, specify a cookbook where the key is located for files/default. Default value is nil, so it will use the cookbook where the resource is used.",
+ desired_state: false
+
+ property :cache_rebuild, [TrueClass, FalseClass],
+ description: "Determines whether to rebuild the APT package cache.",
+ default: true, desired_state: false
default_action :add
allowed_actions :add, :remove
+
+ action_class do
+ LIST_APT_KEY_FINGERPRINTS = %w{apt-key adv --list-public-keys --with-fingerprint --with-colons}.freeze
+
+ # is the provided ID a key ID from a keyserver. Looks at length and HEX only values
+ # @param [String] id the key value passed by the user that *may* be an ID
+ def is_key_id?(id)
+ id = id[2..] if id.start_with?("0x")
+ id =~ /^\h+$/ && [8, 16, 40].include?(id.length)
+ end
+
+ # run the specified command and extract the fingerprints from the output
+ # accepts a command so it can be used to extract both the current key's fingerprints
+ # and the fingerprint of the new key
+ # @param [Array<String>] cmd the command to run
+ #
+ # @return [Array] an array of fingerprints
+ def extract_fingerprints_from_cmd(*cmd)
+ so = shell_out(*cmd)
+ so.stdout.split(/\n/).map do |t|
+ if z = t.match(/^fpr:+([0-9A-F]+):/)
+ z[1].split.join
+ end
+ end.compact
+ end
+
+ # validate the key against the apt keystore to see if that version is expired
+ # @param [String] key
+ #
+ # @return [Boolean] is the key valid or not
+ def key_is_valid?(key)
+ valid = shell_out("apt-key", "list").stdout.each_line.none?(%r{^\/#{key}.*\[expired: .*\]$})
+
+ logger.debug "key #{key} #{valid ? "is valid" : "is not valid"}"
+ valid
+ end
+
+ # return the specified cookbook name or the cookbook containing the
+ # resource.
+ #
+ # @return [String] name of the cookbook
+ def cookbook_name
+ new_resource.cookbook || new_resource.cookbook_name
+ end
+
+ # determine if a cookbook file is available in the run
+ # @param [String] fn the path to the cookbook file
+ #
+ # @return [Boolean] cookbook file exists or doesn't
+ def has_cookbook_file?(fn)
+ run_context.has_cookbook_file_in_cookbook?(cookbook_name, fn)
+ end
+
+ # determine if there are any new keys by comparing the fingerprints of installed
+ # keys to those of the passed file
+ # @param [String] file the keyfile of the new repository
+ #
+ # @return [Boolean] true: no new keys in the file. false: there are new keys
+ def no_new_keys?(file)
+ # Now we are using the option --with-colons that works across old os versions
+ # as well as the latest (16.10). This for both `apt-key` and `gpg` commands
+ installed_keys = extract_fingerprints_from_cmd(*LIST_APT_KEY_FINGERPRINTS)
+ proposed_keys = extract_fingerprints_from_cmd("gpg", "--with-fingerprint", "--with-colons", file)
+ (installed_keys & proposed_keys).sort == proposed_keys.sort
+ end
+
+ # Given the provided key URI determine what kind of chef resource we need
+ # to fetch the key
+ # @param [String] uri the uri of the gpg key (local path or http URL)
+ #
+ # @raise [Chef::Exceptions::FileNotFound] Key isn't remote or found in the current run
+ #
+ # @return [Symbol] :remote_file or :cookbook_file
+ def key_type(uri)
+ if uri.start_with?("http")
+ :remote_file
+ elsif has_cookbook_file?(uri)
+ :cookbook_file
+ else
+ raise Chef::Exceptions::FileNotFound, "Cannot locate key file: #{uri}"
+ end
+ end
+
+ # Fetch the key using either cookbook_file or remote_file, validate it,
+ # and install it with apt-key add
+ # @param [String] key the key to install
+ #
+ # @raise [RuntimeError] Invalid key which can't verify the apt repository
+ #
+ # @return [void]
+ def install_key_from_uri(key)
+ key_name = key.gsub(/[^0-9A-Za-z\-]/, "_")
+ cached_keyfile = ::File.join(Chef::Config[:file_cache_path], key_name)
+ tmp_dir = Dir.mktmpdir(".gpg")
+ at_exit { FileUtils.remove_entry(tmp_dir) }
+
+ declare_resource(key_type(key), cached_keyfile) do
+ source key
+ mode "0644"
+ sensitive new_resource.sensitive
+ action :create
+ verify "gpg --homedir #{tmp_dir} %{path}"
+ end
+
+ execute "apt-key add #{cached_keyfile}" do
+ command [ "apt-key", "add", cached_keyfile ]
+ default_env true
+ sensitive new_resource.sensitive
+ action :run
+ not_if { no_new_keys?(cached_keyfile) }
+ notifies :run, "execute[apt-cache gencaches]", :immediately
+ end
+ end
+
+ # build the apt-key command to install the keyserver
+ # @param [String] key the key to install
+ # @param [String] keyserver the key server to use
+ #
+ # @return [String] the full apt-key command to run
+ def keyserver_install_cmd(key, keyserver)
+ cmd = "apt-key adv --no-tty --recv"
+ cmd << " --keyserver-options http-proxy=#{new_resource.key_proxy}" if new_resource.key_proxy
+ cmd << " --keyserver "
+ cmd << if keyserver.start_with?("hkp://")
+ keyserver
+ else
+ "hkp://#{keyserver}:80"
+ end
+
+ cmd << " #{key}"
+ cmd
+ end
+
+ # @param [String] key
+ # @param [String] keyserver
+ #
+ # @raise [RuntimeError] Invalid key which can't verify the apt repository
+ #
+ # @return [void]
+ def install_key_from_keyserver(key, keyserver = new_resource.keyserver)
+ execute "install-key #{key}" do
+ command keyserver_install_cmd(key, keyserver)
+ default_env true
+ sensitive new_resource.sensitive
+ not_if do
+ present = extract_fingerprints_from_cmd(*LIST_APT_KEY_FINGERPRINTS).any? do |fp|
+ fp.end_with? key.upcase
+ end
+ present && key_is_valid?(key.upcase)
+ end
+ notifies :run, "execute[apt-cache gencaches]", :immediately
+ end
+
+ raise "The key #{key} is invalid and cannot be used to verify an apt repository." unless key_is_valid?(key.upcase)
+ end
+
+ # @param [String] owner
+ # @param [String] repo
+ #
+ # @raise [RuntimeError] Could not access the Launchpad PPA API
+ #
+ # @return [void]
+ def install_ppa_key(owner, repo)
+ url = "https://launchpad.net/api/1.0/~#{owner}/+archive/#{repo}"
+ key_id = Chef::HTTP::Simple.new(url).get("signing_key_fingerprint").delete('"')
+ install_key_from_keyserver(key_id, "keyserver.ubuntu.com")
+ rescue Net::HTTPClientException => e
+ raise "Could not access Launchpad ppa API: #{e.message}"
+ end
+
+ # determine if the repository URL is a PPA
+ # @param [String] url the url of the repository
+ #
+ # @return [Boolean] is the repo URL a PPA
+ def is_ppa_url?(url)
+ url.start_with?("ppa:")
+ end
+
+ # determine the repository's components:
+ # - "components" property if defined
+ # - "main" if "components" not defined and the repo is a PPA URL
+ # - otherwise nothing
+ #
+ # @return [String] the repository component
+ def repo_components
+ if is_ppa_url?(new_resource.uri) && new_resource.components.empty?
+ "main"
+ else
+ new_resource.components
+ end
+ end
+
+ # given a PPA return a PPA URL in http://ppa.launchpad.net format
+ # @param [String] ppa the ppa URL
+ #
+ # @return [String] full PPA URL
+ def make_ppa_url(ppa)
+ owner, repo = ppa[4..-1].split("/")
+ repo ||= "ppa"
+
+ install_ppa_key(owner, repo)
+ "http://ppa.launchpad.net/#{owner}/#{repo}/ubuntu"
+ end
+
+ # build complete repo text that will be written to the config
+ # @param [String] uri
+ # @param [Array] components
+ # @param [Boolean] trusted
+ # @param [String] arch
+ # @param [Boolean] add_src
+ #
+ # @return [String] complete repo config text
+ def build_repo(uri, distribution, components, trusted, arch, add_src = false)
+ uri = make_ppa_url(uri) if is_ppa_url?(uri)
+
+ uri = Addressable::URI.parse(uri)
+ components = Array(components).join(" ")
+ options = []
+ options << "arch=#{arch}" if arch
+ options << "trusted=yes" if trusted
+ optstr = unless options.empty?
+ "[" + options.join(" ") + "]"
+ end
+ info = [ optstr, uri.normalize.to_s, distribution, components ].compact.join(" ")
+ repo = "deb #{info}\n"
+ repo << "deb-src #{info}\n" if add_src
+ repo
+ end
+
+ # clean up a potentially legacy file from before we fixed the usage of
+ # new_resource.name vs. new_resource.repo_name. We might have the
+ # name.list file hanging around and need to clean it up.
+ #
+ # @return [void]
+ def cleanup_legacy_file!
+ legacy_path = "/etc/apt/sources.list.d/#{new_resource.name}.list"
+ if new_resource.name != new_resource.repo_name && ::File.exist?(legacy_path)
+ converge_by "Cleaning up legacy #{legacy_path} repo file" do
+ file legacy_path do
+ action :delete
+ # Not triggering an update since it isn't super likely to be needed.
+ end
+ end
+ end
+ end
+ end
+
+ action :add do
+ return unless debian?
+
+ execute "apt-cache gencaches" do
+ command %w{apt-cache gencaches}
+ default_env true
+ ignore_failure true
+ action :nothing
+ end
+
+ apt_update new_resource.name do
+ ignore_failure true
+ action :nothing
+ end
+
+ if new_resource.key.nil?
+ logger.debug "No 'key' property specified skipping key import"
+ else
+ new_resource.key.each do |k|
+ if is_key_id?(k) && !has_cookbook_file?(k)
+ install_key_from_keyserver(k)
+ else
+ install_key_from_uri(k)
+ end
+ end
+ end
+
+ cleanup_legacy_file!
+
+ repo = build_repo(
+ new_resource.uri,
+ new_resource.distribution,
+ repo_components,
+ new_resource.trusted,
+ new_resource.arch,
+ new_resource.deb_src
+ )
+
+ file "/etc/apt/sources.list.d/#{new_resource.repo_name}.list" do
+ owner "root"
+ group "root"
+ mode "0644"
+ content repo
+ sensitive new_resource.sensitive
+ action :create
+ notifies :run, "execute[apt-cache gencaches]", :immediately
+ notifies :update, "apt_update[#{new_resource.name}]", :immediately if new_resource.cache_rebuild
+ end
+ end
+
+ action :remove do
+ return unless debian?
+
+ cleanup_legacy_file!
+ if ::File.exist?("/etc/apt/sources.list.d/#{new_resource.repo_name}.list")
+ converge_by "Removing #{new_resource.repo_name} repository from /etc/apt/sources.list.d/" do
+ apt_update new_resource.name do
+ ignore_failure true
+ action :nothing
+ end
+
+ file "/etc/apt/sources.list.d/#{new_resource.repo_name}.list" do
+ sensitive new_resource.sensitive
+ action :delete
+ notifies :update, "apt_update[#{new_resource.name}]", :immediately if new_resource.cache_rebuild
+ end
+ end
+ else
+ logger.trace("/etc/apt/sources.list.d/#{new_resource.repo_name}.list does not exist. Nothing to do")
+ end
+ end
+
end
end
end
diff --git a/lib/chef/resource/apt_update.rb b/lib/chef/resource/apt_update.rb
index df2033b063..e5f75143bb 100644
--- a/lib/chef/resource/apt_update.rb
+++ b/lib/chef/resource/apt_update.rb
@@ -1,6 +1,6 @@
#
# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright (c) 2016 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,18 +16,93 @@
# limitations under the License.
#
-require "chef/resource"
+require_relative "../resource"
class Chef
class Resource
class AptUpdate < Chef::Resource
- resource_name :apt_update
- provides :apt_update, os: "linux"
+ unified_mode true
- property :frequency, Integer, default: 86_400
+ provides(:apt_update) { true }
+
+ description "Use the **apt_update** resource to manage APT repository updates on Debian and Ubuntu platforms."
+ introduced "12.7"
+ examples <<~DOC
+ **Update the Apt repository at a specified interval**:
+
+ ```ruby
+ apt_update 'all platforms' do
+ frequency 86400
+ action :periodic
+ end
+ ```
+
+ **Update the Apt repository at the start of a Chef Infra Client run**:
+
+ ```ruby
+ apt_update 'update'
+ ```
+ DOC
+
+ # allow bare apt_update with no name
+ property :name, String, default: ""
+
+ property :frequency, Integer,
+ description: "Determines how frequently (in seconds) APT repository updates are made. Use this property when the `:periodic` action is specified.",
+ default: 86_400
default_action :periodic
allowed_actions :update, :periodic
+
+ action_class do
+ APT_CONF_DIR = "/etc/apt/apt.conf.d".freeze
+ STAMP_DIR = "/var/lib/apt/periodic".freeze
+
+ # Determines whether we need to run `apt-get update`
+ #
+ # @return [Boolean]
+ def apt_up_to_date?
+ ::File.exist?("#{STAMP_DIR}/update-success-stamp") &&
+ ::File.mtime("#{STAMP_DIR}/update-success-stamp") > Time.now - new_resource.frequency
+ end
+
+ def do_update
+ [STAMP_DIR, APT_CONF_DIR].each do |d|
+ directory d do
+ recursive true
+ end
+ end
+
+ file "#{APT_CONF_DIR}/15update-stamp" do
+ content "APT::Update::Post-Invoke-Success {\"touch #{STAMP_DIR}/update-success-stamp 2>/dev/null || true\";};\n"
+ action :create_if_missing
+ end
+
+ execute "apt-get -q update" do
+ command [ "apt-get", "-q", "update" ]
+ default_env true
+ end
+ end
+ end
+
+ action :periodic do
+ return unless debian?
+
+ unless apt_up_to_date?
+ converge_by "update new lists of packages" do
+ do_update
+ end
+ end
+ end
+
+ action :update do
+ return unless debian?
+
+ converge_by "force update new lists of packages" do
+ do_update
+ end
+ end
+
end
end
end
diff --git a/lib/chef/resource/archive_file.rb b/lib/chef/resource/archive_file.rb
new file mode 100644
index 0000000000..4d77ee979b
--- /dev/null
+++ b/lib/chef/resource/archive_file.rb
@@ -0,0 +1,205 @@
+#
+# Copyright:: Copyright (c) Chef Software Inc.
+# Author:: Jamie Winsor (<jamie@vialstudios.com>)
+# Author:: Tim Smith (<tsmith@chef.io>)
+#
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../resource"
+require "fileutils" unless defined?(FileUtils)
+
+class Chef
+ class Resource
+ class ArchiveFile < Chef::Resource
+ unified_mode true
+
+ provides :archive_file
+ provides :libarchive_file # legacy cookbook name
+
+ introduced "15.0"
+ description "Use the **archive_file** resource to extract archive files to disk. This resource uses the libarchive library to extract multiple archive formats including tar, gzip, bzip, and zip formats."
+ examples <<~DOC
+ **Extract a zip file to a specified directory**:
+
+ ```ruby
+ archive_file 'Precompiled.zip' do
+ path '/tmp/Precompiled.zip'
+ destination '/srv/files'
+ end
+ ```
+
+ **Set specific permissions on the extracted files**:
+
+ ```ruby
+ archive_file 'Precompiled.zip' do
+ owner 'tsmith'
+ group 'staff'
+ mode '700'
+ path '/tmp/Precompiled.zip'
+ destination '/srv/files'
+ end
+ ```
+ DOC
+
+ property :path, String,
+ name_property: true,
+ coerce: proc { |f| ::File.expand_path(f) },
+ description: "An optional property to set the file path to the archive to extract if it differs from the resource block's name."
+
+ property :owner, String,
+ description: "The owner of the extracted files."
+
+ property :group, String,
+ description: "The group of the extracted files."
+
+ property :mode, [String, Integer],
+ description: "The mode of the extracted files. Integer values are deprecated as octal values (ex. 0755) would not be interpreted correctly.",
+ default: "755"
+
+ property :destination, String,
+ description: "The file path to extract the archive file to.",
+ required: true
+
+ property :options, [Array, Symbol],
+ description: "An array of symbols representing extraction flags. Example: `:no_overwrite` to prevent overwriting files on disk. By default, this properly sets `:time` which preserves the modification timestamps of files in the archive when writing them to disk.",
+ default: lazy { [:time] }
+
+ property :overwrite, [TrueClass, FalseClass, :auto],
+ description: "Should the resource overwrite the destination file contents if they already exist? If set to `:auto` the date stamp of files within the archive will be compared to those on disk and disk contents will be overwritten if they differ. This may cause unintended consequences if disk date stamps are changed between runs, which will result in the files being overwritten during each client run. Make sure to properly test any change to this property.",
+ default: false
+
+ # backwards compatibility for the legacy cookbook names
+ alias_method :extract_options, :options
+ alias_method :extract_to, :destination
+
+ action :extract do
+ description "Extract and archive file."
+
+ require_libarchive
+
+ unless ::File.exist?(new_resource.path)
+ raise Errno::ENOENT, "No archive found at #{new_resource.path}! Cannot continue."
+ end
+
+ if !::File.exist?(new_resource.destination)
+ Chef::Log.trace("File or directory does not exist at destination path: #{new_resource.destination}")
+
+ converge_by("create directory #{new_resource.destination}") do
+ # @todo when we remove the ability for mode to be an int we can remove the .to_s below
+ FileUtils.mkdir_p(new_resource.destination, mode: new_resource.mode.to_s.to_i(8))
+ end
+
+ extract(new_resource.path, new_resource.destination, Array(new_resource.options))
+ else
+ Chef::Log.trace("File or directory exists at destination path: #{new_resource.destination}.")
+
+ if new_resource.overwrite == true ||
+ (new_resource.overwrite == :auto && archive_differs_from_disk?(new_resource.path, new_resource.destination))
+ Chef::Log.debug("Overwriting existing content at #{new_resource.destination} due to resource's overwrite property settings.")
+
+ extract(new_resource.path, new_resource.destination, Array(new_resource.options))
+ else
+ Chef::Log.debug("Not extracting archive as #{new_resource.destination} exists and resource not set to overwrite.")
+ end
+ end
+
+ if new_resource.owner || new_resource.group
+ converge_by("set owner of files extracted in #{new_resource.destination} to #{new_resource.owner}:#{new_resource.group}") do
+ archive = Archive::Reader.open_filename(new_resource.path)
+ archive.each_entry do |e|
+ FileUtils.chown(new_resource.owner, new_resource.group, "#{new_resource.destination}/#{e.pathname}")
+ end
+ end
+ end
+ end
+
+ action_class do
+ def require_libarchive
+ require "ffi-libarchive"
+ end
+
+ def define_resource_requirements
+ if new_resource.mode.is_a?(Integer)
+ Chef.deprecated(:archive_file_integer_file_mode, "The mode property should be passed to archive_file resources as a String and not an Integer to ensure the value is properly interpreted.")
+ end
+ end
+
+ # This can't be a constant since we might not have required 'ffi-libarchive' yet.
+ def extract_option_map
+ {
+ owner: Archive::EXTRACT_OWNER,
+ permissions: Archive::EXTRACT_PERM,
+ time: Archive::EXTRACT_TIME,
+ no_overwrite: Archive::EXTRACT_NO_OVERWRITE,
+ acl: Archive::EXTRACT_ACL,
+ fflags: Archive::EXTRACT_FFLAGS,
+ extended_information: Archive::EXTRACT_XATTR,
+ xattr: Archive::EXTRACT_XATTR,
+ no_overwrite_newer: Archive::EXTRACT_NO_OVERWRITE_NEWER,
+ }
+ end
+
+ # try to determine if the resource has updated or not by checking for files that are in the
+ # archive, but not on disk or files with a non-matching mtime
+ #
+ # @param [String] src
+ # @param [String] dest
+ #
+ # @return [Boolean]
+ def archive_differs_from_disk?(src, dest)
+ modified = false
+ Dir.chdir(dest) do
+ archive = Archive::Reader.open_filename(src)
+ Chef::Log.trace("Beginning the comparison of file mtime between contents of #{src} and #{dest}")
+ archive.each_entry do |e|
+ pathname = ::File.expand_path(e.pathname)
+ if ::File.exist?(pathname)
+ Chef::Log.trace("#{pathname} mtime is #{::File.mtime(pathname)} and archive is #{e.mtime}")
+ modified = true unless ::File.mtime(pathname) == e.mtime
+ else
+ Chef::Log.trace("#{pathname} doesn't exist on disk, but exists in the archive")
+ modified = true
+ end
+ end
+ end
+ modified
+ end
+
+ # extract the archive
+ #
+ # @param [String] src
+ # @param [String] dest
+ # @param [Array] options
+ #
+ # @return [void]
+ def extract(src, dest, options = [])
+ converge_by("extract #{src} to #{dest}") do
+ flags = [options].flatten.map { |option| extract_option_map[option] }.compact.reduce(:|)
+
+ Dir.chdir(dest) do
+ archive = Archive::Reader.open_filename(src)
+
+ archive.each_entry do |e|
+ archive.extract(e, flags.to_i)
+ end
+ archive.close
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/bash.rb b/lib/chef/resource/bash.rb
index 1238eedc42..9ed6dc68d5 100644
--- a/lib/chef/resource/bash.rb
+++ b/lib/chef/resource/bash.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,12 +16,134 @@
# limitations under the License.
#
-require "chef/resource/script"
-require "chef/provider/script"
+require_relative "script"
class Chef
class Resource
class Bash < Chef::Resource::Script
+ unified_mode true
+
+ provides :bash
+
+ description "Use the **bash** resource to execute scripts using the Bash interpreter. This resource may also use any of the actions and properties that are available to the **execute** resource. Commands that are executed with this resource are (by their nature) not idempotent, as they are typically unique to the environment in which they are run. Use `not_if` and `only_if` to guard this resource for idempotence."
+ examples <<~'DOC'
+ **Compile an application**
+
+ ```ruby
+ bash 'install_something' do
+ user 'root'
+ cwd '/tmp'
+ code <<-EOH
+ wget http://www.example.com/tarball.tar.gz
+ tar -zxf tarball.tar.gz
+ cd tarball
+ ./configure
+ make
+ make install
+ EOH
+ end
+ ```
+
+ **Install a file from a remote location**
+
+ The following is an example of how to install the foo123 module for Nginx. This module adds shell-style functionality to an Nginx configuration file and does the following:
+
+ - Declares three variables
+ - Gets the Nginx file from a remote location
+ - Installs the file using Bash to the path specified by the `src_filepath` variable
+
+ ```ruby
+ src_filename = "foo123-nginx-module-v#{node['nginx']['foo123']['version']}.tar.gz"
+ src_filepath = "#{Chef::Config['file_cache_path']}/#{src_filename}"
+ extract_path = "#{Chef::Config['file_cache_path']}/nginx_foo123_module/#{node['nginx']['foo123']['checksum']}"
+
+ remote_file 'src_filepath' do
+ source node['nginx']['foo123']['url']
+ checksum node['nginx']['foo123']['checksum']
+ owner 'root'
+ group 'root'
+ mode '0755'
+ end
+
+ bash 'extract_module' do
+ cwd ::File.dirname(src_filepath)
+ code <<-EOH
+ mkdir -p #{extract_path}
+ tar xzf #{src_filename} -C #{extract_path}
+ mv #{extract_path}/*/* #{extract_path}/
+ EOH
+ not_if { ::File.exist?(extract_path) }
+ end
+ ```
+
+ **Install an application from git**
+
+ ```ruby
+ git "#{Chef::Config[:file_cache_path]}/ruby-build" do
+ repository 'git://github.com/rbenv/ruby-build.git'
+ revision 'master'
+ action :sync
+ end
+
+ bash 'install_ruby_build' do
+ cwd "#{Chef::Config[:file_cache_path]}/ruby-build"
+ user 'rbenv'
+ group 'rbenv'
+ code <<-EOH
+ ./install.sh
+ EOH
+ environment 'PREFIX' => '/usr/local'
+ end
+ ```
+
+ **Using Attributes in Bash Code**
+
+ The following recipe shows how an attributes file can be used to store certain settings. An attributes file is located in the `attributes/`` directory in the same cookbook as the recipe which calls the attributes file. In this example, the attributes file specifies certain settings for Python that are then used across all nodes against which this recipe will run.
+
+ Python packages have versions, installation directories, URLs, and checksum files. An attributes file that exists to support this type of recipe would include settings like the following:
+
+ ```ruby
+ default['python']['version'] = '2.7.1'
+
+ if python['install_method'] == 'package'
+ default['python']['prefix_dir'] = '/usr'
+ else
+ default['python']['prefix_dir'] = '/usr/local'
+ end
+
+ default['python']['url'] = 'http://www.python.org/ftp/python'
+ default['python']['checksum'] = '80e387...85fd61'
+ ```
+
+ and then the methods in the recipe may refer to these values. A recipe that is used to install Python will need to do the following:
+
+ - Identify each package to be installed (implied in this example, not shown)
+ - Define variables for the package `version` and the `install_path`
+ - Get the package from a remote location, but only if the package does not already exist on the target system
+ - Use the **bash** resource to install the package on the node, but only when the package is not already installed
+
+ ```ruby
+ version = node['python']['version']
+ install_path = "#{node['python']['prefix_dir']}/lib/python#{version.split(/(^\d+\.\d+)/)[1]}"
+
+ remote_file "#{Chef::Config[:file_cache_path]}/Python-#{version}.tar.bz2" do
+ source "#{node['python']['url']}/#{version}/Python-#{version}.tar.bz2"
+ checksum node['python']['checksum']
+ mode '0755'
+ not_if { ::File.exist?(install_path) }
+ end
+
+ bash 'build-and-install-python' do
+ cwd Chef::Config[:file_cache_path]
+ code <<-EOF
+ tar -jxvf Python-#{version}.tar.bz2
+ (cd Python-#{version} && ./configure #{configure_options})
+ (cd Python-#{version} && make && make install)
+ EOF
+ not_if { ::File.exist?(install_path) }
+ end
+ ```
+ DOC
def initialize(name, run_context = nil)
super
diff --git a/lib/chef/resource/batch.rb b/lib/chef/resource/batch.rb
index 10e96839fb..bbb5e73905 100644
--- a/lib/chef/resource/batch.rb
+++ b/lib/chef/resource/batch.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Edwards (<adamed@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,16 +16,21 @@
# limitations under the License.
#
-require "chef/resource/windows_script"
+require_relative "windows_script"
class Chef
class Resource
class Batch < Chef::Resource::WindowsScript
+ unified_mode true
- provides :batch, os: "windows"
+ provides :batch
- def initialize(name, run_context = nil)
- super(name, run_context, nil, "cmd.exe")
+ description "Use the **batch** resource to execute a batch script using the cmd.exe interpreter on Windows. The batch resource creates and executes a temporary file (similar to how the script resource behaves), rather than running the command inline. Commands that are executed with this resource are (by their nature) not idempotent, as they are typically unique to the environment in which they are run. Use `not_if` and `only_if` to guard this resource for idempotence."
+
+ def initialize(*args)
+ super
+ @interpreter = "cmd.exe"
+ @default_guard_interpreter = resource_name
end
end
diff --git a/lib/chef/resource/bff_package.rb b/lib/chef/resource/bff_package.rb
index b14591876a..ffe5dfac1b 100644
--- a/lib/chef/resource/bff_package.rb
+++ b/lib/chef/resource/bff_package.rb
@@ -1,6 +1,6 @@
#
# Author:: Deepali Jagtap (<deepali.jagtap@clogeny.com>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,12 +16,46 @@
# limitations under the License.
#
-require "chef/resource/package"
-require "chef/provider/package/aix"
+require_relative "package"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Resource
class BffPackage < Chef::Resource::Package
+ unified_mode true
+
+ provides :bff_package
+
+ description "Use the **bff_package** resource to manage packages for the AIX platform using the installp utility. When a package is installed from a local file, it must be added to the node using the **remote_file** or **cookbook_file** resources."
+ introduced "12.0"
+ examples <<~DOC
+ The **bff_package** resource is the default package provider on the AIX platform. The base **package** resource may be used, and then when the platform is AIX, #{ChefUtils::Dist::Infra::PRODUCT} will identify the correct package provider. The following examples show how to install part of the IBM XL C/C++ compiler.
+
+ **Installing using the base package resource**
+
+ ```ruby
+ package 'xlccmp.13.1.0' do
+ source '/var/tmp/IBM_XL_C_13.1.0/usr/sys/inst.images/xlccmp.13.1.0'
+ action :install
+ end
+ ```
+
+ **Installing using the bff_package resource**
+
+ ```ruby
+ bff_package 'xlccmp.13.1.0' do
+ source '/var/tmp/IBM_XL_C_13.1.0/usr/sys/inst.images/xlccmp.13.1.0'
+ action :install
+ end
+ ```
+ DOC
+
+ property :package_name, String,
+ description: "An optional property to set the package name if it differs from the resource block's name.",
+ identity: true
+
+ property :version, String,
+ description: "The version of a package to be installed or upgraded."
end
end
end
diff --git a/lib/chef/resource/breakpoint.rb b/lib/chef/resource/breakpoint.rb
index a5eed0da94..50e2d06391 100644
--- a/lib/chef/resource/breakpoint.rb
+++ b/lib/chef/resource/breakpoint.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@kallistec.com>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,17 +16,91 @@
# limitations under the License.
#
-require "chef/resource"
+require_relative "../resource"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Resource
class Breakpoint < Chef::Resource
+ unified_mode true
+
+ provides :breakpoint, target_mode: true
+
+ description "Use the **breakpoint** resource to add breakpoints to recipes. Run the #{ChefUtils::Dist::Infra::SHELL} in #{ChefUtils::Dist::Infra::PRODUCT} mode, and then use those breakpoints to debug recipes. Breakpoints are ignored by the #{ChefUtils::Dist::Infra::CLIENT} during an actual #{ChefUtils::Dist::Infra::CLIENT} run. That said, breakpoints are typically used to debug recipes only when running them in a non-production environment, after which they are removed from those recipes before the parent cookbook is uploaded to the Chef server."
+ introduced "12.0"
+ examples <<~DOC
+ **A recipe without a breakpoint**
+
+ ```ruby
+ yum_key node['yum']['elrepo']['key'] do
+ url node['yum']['elrepo']['key_url']
+ action :add
+ end
+
+ yum_repository 'elrepo' do
+ description 'ELRepo.org Community Enterprise Linux Extras Repository'
+ key node['yum']['elrepo']['key']
+ mirrorlist node['yum']['elrepo']['url']
+ includepkgs node['yum']['elrepo']['includepkgs']
+ exclude node['yum']['elrepo']['exclude']
+ action :create
+ end
+ ```
+
+ **The same recipe with breakpoints**
+
+ In the following example, the name of each breakpoint is an arbitrary string.
+
+ ```ruby
+ breakpoint "before yum_key node['yum']['repo_name']['key']" do
+ action :break
+ end
+
+ yum_key node['yum']['repo_name']['key'] do
+ url node['yum']['repo_name']['key_url']
+ action :add
+ end
+
+ breakpoint "after yum_key node['yum']['repo_name']['key']" do
+ action :break
+ end
+
+ breakpoint "before yum_repository 'repo_name'" do
+ action :break
+ end
+
+ yum_repository 'repo_name' do
+ description 'description'
+ key node['yum']['repo_name']['key']
+ mirrorlist node['yum']['repo_name']['url']
+ includepkgs node['yum']['repo_name']['includepkgs']
+ exclude node['yum']['repo_name']['exclude']
+ action :create
+ end
+
+ breakpoint "after yum_repository 'repo_name'" do
+ action :break
+ end
+ ```
+
+ In the previous examples, the names are used to indicate if the breakpoint is before or after a resource and also to specify which resource it is before or after.
+ DOC
+
default_action :break
def initialize(action = "break", *args)
super(caller.first, *args)
end
+ action :break do
+ if defined?(Shell) && Shell.running?
+ with_run_context :parent do
+ run_context.resource_collection.iterator.pause
+ new_resource.updated_by_last_action(true)
+ run_context.resource_collection.iterator
+ end
+ end
+ end
end
end
end
diff --git a/lib/chef/resource/build_essential.rb b/lib/chef/resource/build_essential.rb
new file mode 100644
index 0000000000..3039f709c8
--- /dev/null
+++ b/lib/chef/resource/build_essential.rb
@@ -0,0 +1,187 @@
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class BuildEssential < Chef::Resource
+ unified_mode true
+
+ provides(:build_essential) { true }
+
+ description "Use the **build_essential** resource to install the packages required for compiling C software from source."
+ introduced "14.0"
+ examples <<~DOC
+ **Install compilation packages**:
+
+ ```ruby
+ build_essential
+ ```
+
+ **Install compilation packages during the compilation phase**:
+
+ ```ruby
+ build_essential 'Install compilation tools' do
+ compile_time true
+ end
+ ```
+
+ **Upgrade compilation packages on macOS systems**:
+
+ ```ruby
+ build_essential 'Install compilation tools' do
+ action :upgrade
+ end
+ ```
+ DOC
+
+ # this allows us to use build_essential without setting a name
+ property :name, String, default: ""
+
+ property :raise_if_unsupported, [TrueClass, FalseClass],
+ description: "Raise a hard error on platforms where this resource is unsupported.",
+ introduced: "15.5",
+ default: false, desired_state: false # FIXME: make this default to true
+
+ action :install do
+
+ description "Install build essential packages"
+
+ case
+ when debian?
+ package %w{ autoconf binutils-doc bison build-essential flex gettext ncurses-dev }
+ when fedora_derived?
+ package %w{ autoconf bison flex gcc gcc-c++ gettext kernel-devel make m4 ncurses-devel patch }
+ when freebsd?
+ package "devel/gmake"
+ package "devel/autoconf"
+ package "devel/m4"
+ package "devel/gettext"
+ when macos?
+ install_xcode_cli_tools(xcode_cli_package_label) unless xcode_cli_installed?
+ when omnios?
+ package "developer/gcc48"
+ package "developer/object-file"
+ package "developer/linker"
+ package "developer/library/lint"
+ package "developer/build/gnu-make"
+ package "system/header"
+ package "system/library/math/header-math"
+
+ # Per OmniOS documentation, the gcc bin dir isn't in the default
+ # $PATH, so add it to the running process environment
+ # http://omnios.omniti.com/wiki.php/DevEnv
+ ENV["PATH"] = "#{ENV["PATH"]}:/opt/gcc-4.7.2/bin"
+ when solaris2?
+ package "autoconf"
+ package "automake"
+ package "bison"
+ package "gnu-coreutils"
+ package "flex"
+ package "gcc"
+ package "gnu-grep"
+ package "gnu-make"
+ package "gnu-patch"
+ package "gnu-tar"
+ package "make"
+ package "pkg-config"
+ package "ucb"
+ when smartos?
+ package "autoconf"
+ package "binutils"
+ package "build-essential"
+ package "gcc47"
+ package "gmake"
+ package "pkg-config"
+ when suse?
+ package %w{ autoconf bison flex gcc gcc-c++ kernel-default-devel make m4 }
+ else
+ msg = <<-EOH
+ The build_essential resource does not currently support the '#{node["platform_family"]}'
+ platform family. Skipping...
+ EOH
+ if new_resource.raise_if_unsupported
+ raise msg
+ else
+ Chef::Log.warn msg
+ end
+ end
+ end
+
+ action :upgrade do
+ description "Upgrade build essential (Xcode Command Line) tools on macOS"
+
+ if macos?
+ pkg_label = xcode_cli_package_label
+
+ # With upgrade action we should install if it's not installed or if there's an available update.
+ # `pkg_label` will be nil if there's no update.
+ install_xcode_cli_tools(pkg_label) if !xcode_cli_installed? || pkg_label
+ else
+ Chef::Log.info "The build_essential resource :upgrade action is only supported on macOS systems. Skipping..."
+ end
+ end
+
+ action_class do
+ #
+ # Install Xcode Command Line tools via softwareupdate CLI
+ #
+ # @param [String] label The label (package name) to install
+ #
+ def install_xcode_cli_tools(label)
+ # This script was graciously borrowed and modified from Tim Sutton's
+ # osx-vm-templates at https://github.com/timsutton/osx-vm-templates/blob/b001475df54a9808d3d56d06e71b8fa3001fff42/scripts/xcode-cli-tools.sh
+ bash "install Xcode Command Line Tools" do
+ code <<-EOH
+ # create the placeholder file that's checked by CLI updates' .dist code
+ # in Apple's SUS catalog
+ touch /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress
+ # install it
+ softwareupdate -i "#{label}" --verbose
+ # Remove the placeholder to prevent perpetual appearance in the update utility
+ rm -f /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress
+ EOH
+ end
+ end
+
+ #
+ # Determine if the XCode Command Line Tools are installed by checking
+ # for success from `xcode-select -p`
+ #
+ # @return [true, false]
+ def xcode_cli_installed?
+ !shell_out("xcode-select", "-p").error?
+ end
+
+ #
+ # Return to package label of the latest Xcode Command Line Tools update, if available
+ #
+ # @return [String, NilClass]
+ def xcode_cli_package_label
+ available_updates = shell_out("softwareupdate", "--list")
+
+ # raise if we fail to check
+ available_updates.error!
+
+ # https://rubular.com/r/UPEE5P7mZLvXNs
+ # this will return the match or nil
+ available_updates.stdout[/^\s*\* (?:Label: )?(Command Line Tools.*)/, 1]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/cab_package.rb b/lib/chef/resource/cab_package.rb
new file mode 100644
index 0000000000..904fe81701
--- /dev/null
+++ b/lib/chef/resource/cab_package.rb
@@ -0,0 +1,81 @@
+#
+# Author:: Vasundhara Jagdale (<vasundhara.jagdale@msystechnologies.com>)
+# Copyright:: Copyright (c) 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_relative "package"
+require_relative "../mixin/uris"
+
+class Chef
+ class Resource
+ class CabPackage < Chef::Resource::Package
+ include Chef::Mixin::Uris
+ unified_mode true
+
+ provides :cab_package
+
+ description "Use the **cab_package** resource to install or remove Microsoft Windows cabinet (.cab) packages."
+ introduced "12.15"
+ examples <<~'DOC'
+ **Using local path in source**
+
+ ```ruby
+ cab_package 'Install .NET 3.5 sp1 via KB958488' do
+ source 'C:\Users\xyz\AppData\Local\Temp\Windows6.1-KB958488-x64.cab'
+ action :install
+ end
+
+ cab_package 'Remove .NET 3.5 sp1 via KB958488' do
+ source 'C:\Users\xyz\AppData\Local\Temp\Windows6.1-KB958488-x64.cab'
+ action :remove
+ end
+ ```
+
+ **Using URL in source**
+
+ ```ruby
+ cab_package 'Install .NET 3.5 sp1 via KB958488' do
+ source 'https://s3.amazonaws.com/my_bucket/Windows6.1-KB958488-x64.cab'
+ action :install
+ end
+
+ cab_package 'Remove .NET 3.5 sp1 via KB958488' do
+ source 'https://s3.amazonaws.com/my_bucket/Temp\Windows6.1-KB958488-x64.cab'
+ action :remove
+ end
+ ```
+ DOC
+
+ allowed_actions :install, :remove
+
+ property :package_name, String,
+ description: "An optional property to set the package name if it differs from the resource block's name.",
+ identity: true
+
+ property :version, String,
+ description: "The version of a package to be installed or upgraded."
+
+ property :source, String,
+ description: "The local file path or URL for the CAB package.",
+ coerce: (proc do |s|
+ unless s.nil?
+ uri_scheme?(s) ? s : Chef::Util::PathHelper.canonical_path(s, false)
+ end
+ end),
+ default: lazy { package_name }, default_description: "The package name."
+ end
+ end
+end
diff --git a/lib/chef/resource/chef_client_config.rb b/lib/chef/resource/chef_client_config.rb
new file mode 100644
index 0000000000..b3ea86c476
--- /dev/null
+++ b/lib/chef/resource/chef_client_config.rb
@@ -0,0 +1,313 @@
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Resource
+ class ChefClientConfig < Chef::Resource
+ unified_mode true
+
+ provides :chef_client_config
+
+ description "Use the **chef_client_config** resource to create a client.rb file in the #{ChefUtils::Dist::Infra::PRODUCT} configuration directory. See the [client.rb docs](https://docs.chef.io/config_rb_client/) for more details on options available in the client.rb configuration file."
+ introduced "16.6"
+ examples <<~DOC
+ **Bare minimum #{ChefUtils::Dist::Infra::PRODUCT} client.rb**:
+
+ The absolute minimum configuration necessary for a node to communicate with the Infra Server is the URL of the Infra Server. All other configuration options either have values at the server side (Policyfiles, Roles, Environments, etc) or have default values determined at client startup.
+
+ ```ruby
+ chef_client_config 'Create client.rb' do
+ chef_server_url 'https://chef.example.dmz'
+ end
+ ```
+
+ **More complex #{ChefUtils::Dist::Infra::PRODUCT} client.rb**:
+
+ ```ruby
+ chef_client_config 'Create client.rb' do
+ chef_server_url 'https://chef.example.dmz'
+ log_level :info
+ log_location :syslog
+ http_proxy 'proxy.example.dmz'
+ https_proxy 'proxy.example.dmz'
+ no_proxy %w(internal.example.dmz)
+ end
+ ```
+
+ **Adding additional config content to the client.rb**:
+
+ This resource aims to provide common configuration options. Some configuration options are missing and some users may want to use arbitrary Ruby code within their configuration. For this we offer an `additional_config` property that can be used to add any configuration or code to the bottom of the `client.rb` file. Also keep in mind that within the configuration directory is a `client.d` directory where you can put additional `.rb` files containing configuration options. These can be created using `file` or `template` resources within your cookbooks as necessary.
+
+ ```ruby
+ chef_client_config 'Create client.rb' do
+ chef_server_url 'https://chef.example.dmz'
+ additional_config <<~CONFIG
+ # Extra config code to safely load a gem into the client run.
+ # Since the config is Ruby you can run any Ruby code you want via the client.rb.
+ # It's a great way to break things, so be careful
+ begin
+ require 'aws-sdk'
+ rescue LoadError
+ Chef::Log.warn "Failed to load aws-sdk."
+ end
+ CONFIG
+ end
+ ```
+
+ **Setup two report handlers in the client.rb**:
+
+ ```ruby
+ chef_client_config 'Create client.rb' do
+ chef_server_url 'https://chef.example.dmz'
+ report_handlers [
+ {
+ 'class' => 'ReportHandler1Class',
+ 'arguments' => ["'FirstArgument'", "'SecondArgument'"],
+ },
+ {
+ 'class' => 'ReportHandler2Class',
+ 'arguments' => ["'FirstArgument'", "'SecondArgument'"],
+ },
+ ]
+ end
+ ```
+ DOC
+
+ # @todo policy_file or policy_group being set requires the other to be set so enforce that.
+ # @todo all properties for automate report
+ # @todo add all descriptions
+ # @todo validate handler hash structure
+
+ #
+ # @param [String, Symbol] prop_val the value from the property
+ #
+ # @return [Symbol] The symbol form of the symbol-like string, string, or symbol value
+ #
+ def string_to_symbol(prop_val)
+ if prop_val.is_a?(String) && prop_val.start_with?(":")
+ prop_val[1..-1].to_sym
+ else
+ prop_val.to_sym
+ end
+ end
+
+ property :config_directory, String,
+ description: "The directory to store the client.rb in.",
+ default: ChefConfig::Config.etc_chef_dir,
+ default_description: "`/etc/chef/` on *nix-like systems and `C:\\chef\\` on Windows"
+
+ property :user, String,
+ description: "The user that should own the client.rb file and the configuration directory if it needs to be created. Note: The configuration directory will not be created if it already exists, which allows you to further control the setup of that directory outside of this resource."
+
+ property :group, String,
+ description: "The group that should own the client.rb file and the configuration directory if it needs to be created. Note: The configuration directory will not be created if it already exists, which allows you to further control the setup of that directory outside of this resource."
+
+ property :node_name, [String, NilClass], # this accepts nil so people can disable the default
+ description: "The name of the node. This configuration sets the `node.name` value used in cookbooks and the `client_name` value used when authenticating to a #{ChefUtils::Dist::Server::PRODUCT} to determine what configuration to apply. Note: By default this configuration uses the `node.name` value which would be set during bootstrap. Hard coding this value in the `client.rb` config avoids logic within #{ChefUtils::Dist::Server::PRODUCT} that performs DNS lookups and may fail in the event of a DNS outage. To skip this default value and instead use the built-in #{ChefUtils::Dist::Server::PRODUCT} logic, set this property to `nil`",
+ default: lazy { node.name },
+ default_description: "The `node.name` value reported by #{ChefUtils::Dist::Infra::PRODUCT}."
+
+ property :chef_server_url, String,
+ description: "The URL for the #{ChefUtils::Dist::Server::PRODUCT}.",
+ required: true
+
+ # @todo Allow passing this as a string and convert it to the symbol
+ property :ssl_verify_mode, [Symbol, String],
+ equal_to: %i{verify_none verify_peer},
+ coerce: proc { |x| string_to_symbol(x) },
+ description: <<~DESC
+ Set the verify mode for HTTPS requests.
+
+ * Use :verify_none for no validation of SSL certificates.
+ * Use :verify_peer for validation of all SSL certificates, including the #{ChefUtils::Dist::Server::PRODUCT} connections, S3 connections, and any HTTPS remote_file resource URLs used in #{ChefUtils::Dist::Infra::PRODUCT} runs. This is the recommended setting.
+ DESC
+
+ property :formatters, Array,
+ description: "",
+ default: []
+
+ property :event_loggers, Array,
+ description: "",
+ default: []
+
+ property :log_level, Symbol,
+ description: "The level of logging performed by the #{ChefUtils::Dist::Infra::PRODUCT}.",
+ equal_to: %i{auto trace debug info warn fatal}
+
+ property :log_location, [String, Symbol],
+ description: "The location to save logs to. This can either by a path to a log file on disk `:syslog` to log to Syslog, `:win_evt` to log to the Windows Event Log, or `'STDERR'`/`'STDOUT'` to log to the *nix text streams.",
+ callbacks: {
+ "accepts Symbol values of ':win_evt' for Windows Event Log or ':syslog' for Syslog" => lambda { |p|
+ p.is_a?(Symbol) ? %i{win_evt syslog}.include?(p) : true
+ },
+ }
+
+ property :http_proxy, String,
+ description: "The proxy server to use for HTTP connections."
+
+ property :https_proxy, String,
+ description: "The proxy server to use for HTTPS connections."
+
+ property :ftp_proxy, String,
+ description: "The proxy server to use for FTP connections."
+
+ property :no_proxy, [String, Array],
+ description: "A comma-separated list or an array of URLs that do not need a proxy.",
+ coerce: proc { |x| x.is_a?(Array) ? x.join(",") : x },
+ default: []
+
+ # @todo we need to fixup bad plugin naming inputs here
+ property :ohai_disabled_plugins, Array,
+ description: "Ohai plugins that should be disabled in order to speed up the #{ChefUtils::Dist::Infra::PRODUCT} run and reduce the size of node data sent to #{ChefUtils::Dist::Infra::PRODUCT}",
+ coerce: proc { |x| x.map { |v| string_to_symbol(v).capitalize } },
+ default: []
+
+ # @todo we need to fixup bad plugin naming inputs here
+ property :ohai_optional_plugins, Array,
+ description: "Optional Ohai plugins that should be enabled to provide additional Ohai data for use in cookbooks.",
+ coerce: proc { |x| x.map { |v| string_to_symbol(v).capitalize } },
+ default: []
+
+ property :minimal_ohai, [true, false],
+ description: "Run a minimal set of Ohai plugins providing data necessary for the execution of #{ChefUtils::Dist::Infra::PRODUCT}'s built-in resources. Setting this to true will skip many large and time consuming data sets such as `cloud` or `packages`. Setting this this to true may break cookbooks that assume all Ohai data will be present."
+
+ property :start_handlers, Array,
+ description: %q(An array of hashes that contain a report handler class and the arguments to pass to that class on initialization. The hash should include `class` and `argument` keys where `class` is a String and `argument` is an array of quoted String values. For example: `[{'class' => 'MyHandler', %w('"argument1"', '"argument2"')}]`),
+ default: []
+
+ property :report_handlers, Array,
+ description: %q(An array of hashes that contain a report handler class and the arguments to pass to that class on initialization. The hash should include `class` and `argument` keys where `class` is a String and `argument` is an array of quoted String values. For example: `[{'class' => 'MyHandler', %w('"argument1"', '"argument2"')}]`),
+ default: []
+
+ property :exception_handlers, Array,
+ description: %q(An array of hashes that contain a exception handler class and the arguments to pass to that class on initialization. The hash should include `class` and `argument` keys where `class` is a String and `argument` is an array of quoted String values. For example: `[{'class' => 'MyHandler', %w('"argument1"', '"argument2"')}]`),
+ default: []
+
+ property :chef_license, String,
+ description: "Accept the [Chef EULA](https://www.chef.io/end-user-license-agreement/)",
+ equal_to: %w{accept accept-no-persist accept-silent}
+
+ property :policy_name, String,
+ description: "The name of a policy, as identified by the `name` setting in a Policyfile.rb file. `policy_group` when setting this property."
+
+ property :policy_group, String,
+ description: "The name of a `policy group` that exists on the #{ChefUtils::Dist::Server::PRODUCT}. `policy_name` must also be specified when setting this property."
+
+ property :named_run_list, String,
+ description: "A specific named runlist defined in the node's applied Policyfile, which the should be used when running #{ChefUtils::Dist::Infra::PRODUCT}."
+
+ property :pid_file, String,
+ description: "The location in which a process identification number (pid) is saved. An executable, when started as a daemon, writes the pid to the specified file. "
+
+ property :file_cache_path, String,
+ description: "The location in which cookbooks (and other transient data) files are stored when they are synchronized. This value can also be used in recipes to download files with the `remote_file` resource."
+
+ property :file_backup_path, String,
+ description: "The location in which backup files are stored. If this value is empty, backup files are stored in the directory of the target file"
+
+ property :file_staging_uses_destdir, String,
+ description: "How file staging (via temporary files) is done. When `true`, temporary files are created in the directory in which files will reside. When `false`, temporary files are created under `ENV['TMP']`"
+
+ property :additional_config, String,
+ description: "Additional text to add at the bottom of the client.rb config. This can be used to run custom Ruby or to add less common config options"
+
+ action :create do
+ unless ::Dir.exist?(new_resource.config_directory)
+ directory new_resource.config_directory do
+ user new_resource.user unless new_resource.user.nil?
+ group new_resource.group unless new_resource.group.nil?
+ mode "0750"
+ recursive true
+ end
+ end
+
+ unless ::Dir.exist?(::File.join(new_resource.config_directory, "client.d"))
+ directory ::File.join(new_resource.config_directory, "client.d") do
+ user new_resource.user unless new_resource.user.nil?
+ group new_resource.group unless new_resource.group.nil?
+ mode "0750"
+ recursive true
+ end
+ end
+
+ template ::File.join(new_resource.config_directory, "client.rb") do
+ source ::File.expand_path("support/client.erb", __dir__)
+ user new_resource.user unless new_resource.user.nil?
+ group new_resource.group unless new_resource.group.nil?
+ local true
+ variables(
+ chef_license: new_resource.chef_license,
+ chef_server_url: new_resource.chef_server_url,
+ event_loggers: new_resource.event_loggers,
+ exception_handlers: format_handler(new_resource.exception_handlers),
+ file_backup_path: new_resource.file_backup_path,
+ file_cache_path: new_resource.file_cache_path,
+ file_staging_uses_destdir: new_resource.file_staging_uses_destdir,
+ formatters: new_resource.formatters,
+ http_proxy: new_resource.http_proxy,
+ https_proxy: new_resource.https_proxy,
+ ftp_proxy: new_resource.ftp_proxy,
+ log_level: new_resource.log_level,
+ log_location: new_resource.log_location,
+ minimal_ohai: new_resource.minimal_ohai,
+ named_run_list: new_resource.named_run_list,
+ no_proxy: new_resource.no_proxy,
+ node_name: new_resource.node_name,
+ ohai_disabled_plugins: new_resource.ohai_disabled_plugins,
+ ohai_optional_plugins: new_resource.ohai_optional_plugins,
+ pid_file: new_resource.pid_file,
+ policy_group: new_resource.policy_group,
+ policy_name: new_resource.policy_name,
+ report_handlers: format_handler(new_resource.report_handlers),
+ ssl_verify_mode: new_resource.ssl_verify_mode,
+ start_handlers: format_handler(new_resource.start_handlers),
+ additional_config: new_resource.additional_config
+ )
+ mode "0640"
+ action :create
+ end
+ end
+
+ action :remove do
+ file ::File.join(new_resource.config_directory, "client.rb") do
+ action :delete
+ end
+ end
+
+ action_class do
+ #
+ # Format the handler document in the way we want it presented in the client.rb file
+ #
+ # @param [Hash] a handler property
+ #
+ # @return [Array] Array of handler data
+ #
+ def format_handler(handler_property)
+ handler_data = []
+
+ handler_property.each do |handler|
+ handler_data << "#{handler["class"]}.new(#{handler["arguments"].join(",")})"
+ end
+
+ handler_data
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/chef_client_cron.rb b/lib/chef/resource/chef_client_cron.rb
new file mode 100644
index 0000000000..003b28d7b3
--- /dev/null
+++ b/lib/chef/resource/chef_client_cron.rb
@@ -0,0 +1,235 @@
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+require_relative "helpers/cron_validations"
+require "digest/md5" unless defined?(Digest::MD5)
+
+class Chef
+ class Resource
+ class ChefClientCron < Chef::Resource
+ unified_mode true
+
+ provides :chef_client_cron
+
+ description "Use the **chef_client_cron** resource to setup the #{ChefUtils::Dist::Infra::PRODUCT} to run as a cron job. This resource will also create the specified log directory if it doesn't already exist."
+ introduced "16.0"
+ examples <<~DOC
+ **Setup #{ChefUtils::Dist::Infra::PRODUCT} to run using the default 30 minute cadence**:
+
+ ```ruby
+ chef_client_cron 'Run #{ChefUtils::Dist::Infra::PRODUCT} as a cron job'
+ ```
+
+ **Run #{ChefUtils::Dist::Infra::PRODUCT} twice a day**:
+
+ ```ruby
+ chef_client_cron 'Run #{ChefUtils::Dist::Infra::PRODUCT} every 12 hours' do
+ minute 0
+ hour '0,12'
+ end
+ ```
+
+ **Run #{ChefUtils::Dist::Infra::PRODUCT} with extra options passed to the client**:
+
+ ```ruby
+ chef_client_cron 'Run an override recipe' do
+ daemon_options ['--override-runlist mycorp_base::default']
+ end
+ ```
+ DOC
+
+ extend Chef::ResourceHelpers::CronValidations
+
+ property :job_name, String,
+ default: ChefUtils::Dist::Infra::CLIENT,
+ description: "The name of the cron job to create."
+
+ property :comment, String,
+ description: "A comment to place in the cron.d file."
+
+ property :user, String,
+ description: "The name of the user that #{ChefUtils::Dist::Infra::PRODUCT} runs as.",
+ default: "root"
+
+ property :minute, [Integer, String],
+ description: "The minute at which #{ChefUtils::Dist::Infra::PRODUCT} is to run (0 - 59) or a cron pattern such as '0,30'.",
+ default: "0,30", callbacks: {
+ "should be a valid minute spec" => method(:validate_minute),
+ }
+
+ property :hour, [Integer, String],
+ description: "The hour at which #{ChefUtils::Dist::Infra::PRODUCT} is to run (0 - 23) or a cron pattern such as '0,12'.",
+ default: "*", callbacks: {
+ "should be a valid hour spec" => method(:validate_hour),
+ }
+
+ property :day, [Integer, String],
+ description: "The day of month at which #{ChefUtils::Dist::Infra::PRODUCT} is to run (1 - 31) or a cron pattern such as '1,7,14,21,28'.",
+ default: "*", callbacks: {
+ "should be a valid day spec" => method(:validate_day),
+ }
+
+ property :month, [Integer, String],
+ description: "The month in the year on which #{ChefUtils::Dist::Infra::PRODUCT} is to run (1 - 12, jan-dec, or *).",
+ default: "*", callbacks: {
+ "should be a valid month spec" => method(:validate_month),
+ }
+
+ property :weekday, [Integer, String],
+ description: "The day of the week on which #{ChefUtils::Dist::Infra::PRODUCT} is to run (0-7, mon-sun, or *), where Sunday is both 0 and 7.",
+ default: "*", callbacks: {
+ "should be a valid weekday spec" => method(:validate_dow),
+ }
+
+ property :splay, [Integer, String],
+ default: 300,
+ coerce: proc { |x| Integer(x) },
+ callbacks: { "should be a positive number" => proc { |v| v > 0 } },
+ description: "A random number of seconds between 0 and X to add to interval so that all #{ChefUtils::Dist::Infra::CLIENT} commands don't execute at the same time."
+
+ property :mailto, String,
+ description: "The e-mail address to e-mail any cron task failures to."
+
+ property :accept_chef_license, [true, false],
+ description: "Accept the Chef Online Master License and Services Agreement. See <https://www.chef.io/online-master-agreement/>",
+ default: false
+
+ property :config_directory, String,
+ default: ChefConfig::Config.etc_chef_dir,
+ description: "The path of the config directory."
+
+ property :log_directory, String,
+ default: lazy { platform?("mac_os_x") ? "/Library/Logs/#{ChefUtils::Dist::Infra::DIR_SUFFIX.capitalize}" : "/var/log/#{ChefUtils::Dist::Infra::DIR_SUFFIX}" },
+ default_description: "/Library/Logs/#{ChefUtils::Dist::Infra::DIR_SUFFIX.capitalize} on macOS and /var/log/#{ChefUtils::Dist::Infra::DIR_SUFFIX} otherwise",
+ description: "The path of the directory to create the log file in."
+
+ property :log_file_name, String,
+ default: "client.log",
+ description: "The name of the log file to use."
+
+ property :append_log_file, [true, false],
+ default: true,
+ description: "Append to the log file instead of overwriting the log file on each run."
+
+ property :chef_binary_path, String,
+ default: "/opt/#{ChefUtils::Dist::Infra::DIR_SUFFIX}/bin/#{ChefUtils::Dist::Infra::CLIENT}",
+ description: "The path to the #{ChefUtils::Dist::Infra::CLIENT} binary."
+
+ property :daemon_options, Array,
+ default: lazy { [] },
+ description: "An array of options to pass to the #{ChefUtils::Dist::Infra::CLIENT} command."
+
+ property :environment, Hash,
+ default: lazy { {} },
+ description: "A Hash containing additional arbitrary environment variables under which the cron job will be run in the form of `({'ENV_VARIABLE' => 'VALUE'})`."
+
+ property :nice, [Integer, String],
+ description: "The process priority to run the #{ChefUtils::Dist::Infra::CLIENT} process at. A value of -20 is the highest priority and 19 is the lowest priority.",
+ introduced: "16.5",
+ coerce: proc { |x| Integer(x) },
+ callbacks: { "should be an Integer between -20 and 19" => proc { |v| v >= -20 && v <= 19 } }
+
+ action :add do
+ # TODO: Replace this with a :create_if_missing action on directory when that exists
+ unless ::Dir.exist?(new_resource.log_directory)
+ directory new_resource.log_directory do
+ owner new_resource.user
+ mode "0750"
+ recursive true
+ end
+ end
+
+ declare_resource(cron_resource_type, new_resource.job_name) do
+ minute new_resource.minute
+ hour new_resource.hour
+ day new_resource.day
+ weekday new_resource.weekday
+ month new_resource.month
+ environment new_resource.environment
+ mailto new_resource.mailto if new_resource.mailto
+ user new_resource.user
+ comment new_resource.comment if new_resource.comment
+ command client_command
+ end
+ end
+
+ action :remove do
+ declare_resource(cron_resource_type, new_resource.job_name) do
+ action :delete
+ end
+ end
+
+ action_class do
+ #
+ # Generate a uniformly distributed unique number to sleep from 0 to the splay time
+ #
+ # @param [Integer] splay The number of seconds to splay
+ #
+ # @return [Integer]
+ #
+ def splay_sleep_time(splay)
+ seed = node["shard_seed"] || Digest::MD5.hexdigest(node.name).to_s.hex
+ random = Random.new(seed.to_i)
+ random.rand(splay)
+ end
+
+ #
+ # The complete cron command to run
+ #
+ # @return [String]
+ #
+ def client_command
+ cmd = ""
+ cmd << "/bin/sleep #{splay_sleep_time(new_resource.splay)}; "
+ cmd << "#{which("nice")} -n #{new_resource.nice} " if new_resource.nice
+ cmd << "#{new_resource.chef_binary_path} "
+ cmd << "#{new_resource.daemon_options.join(" ")} " unless new_resource.daemon_options.empty?
+ cmd << "-c #{::File.join(new_resource.config_directory, "client.rb")} "
+ cmd << "--chef-license accept " if new_resource.accept_chef_license
+ cmd << log_command
+ cmd << " || echo \"#{ChefUtils::Dist::Infra::PRODUCT} execution failed\"" if new_resource.mailto
+ cmd
+ end
+
+ #
+ # The portion of the overall cron job that handles logging based on the append_log_file property
+ #
+ # @return [String]
+ #
+ def log_command
+ if new_resource.append_log_file
+ "-L #{::File.join(new_resource.log_directory, new_resource.log_file_name)}"
+ else
+ "> #{::File.join(new_resource.log_directory, new_resource.log_file_name)} 2>&1"
+ end
+ end
+
+ #
+ # The type of cron resource to run. Linux systems all support the /etc/cron.d directory
+ # and can use the cron_d resource, but Solaris / AIX / FreeBSD need to use the crontab
+ # via the legacy cron resource.
+ #
+ # @return [Symbol]
+ #
+ def cron_resource_type
+ linux? ? :cron_d : :cron
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/chef_client_launchd.rb b/lib/chef/resource/chef_client_launchd.rb
new file mode 100644
index 0000000000..0e173050d0
--- /dev/null
+++ b/lib/chef/resource/chef_client_launchd.rb
@@ -0,0 +1,194 @@
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+class Chef
+ class Resource
+ class ChefClientLaunchd < Chef::Resource
+ unified_mode true
+
+ provides :chef_client_launchd
+
+ description "Use the **chef_client_launchd** resource to configure the #{ChefUtils::Dist::Infra::PRODUCT} to run on a schedule."
+ introduced "16.5"
+ examples <<~DOC
+ **Set the #{ChefUtils::Dist::Infra::PRODUCT} to run on a schedule**:
+
+ ```ruby
+ chef_client_launchd 'Setup the #{ChefUtils::Dist::Infra::PRODUCT} to run every 30 minutes' do
+ interval 30
+ action :enable
+ end
+ ```
+
+ **Disable the #{ChefUtils::Dist::Infra::PRODUCT} running on a schedule**:
+
+ ```ruby
+ chef_client_launchd 'Prevent the #{ChefUtils::Dist::Infra::PRODUCT} from running on a schedule' do
+ action :disable
+ end
+ ```
+ DOC
+
+ property :user, String,
+ description: "The name of the user that #{ChefUtils::Dist::Infra::PRODUCT} runs as.",
+ default: "root"
+
+ property :working_directory, String,
+ description: "The working directory to run the #{ChefUtils::Dist::Infra::PRODUCT} from.",
+ default: "/var/root"
+
+ property :interval, [Integer, String],
+ description: "Time in minutes between #{ChefUtils::Dist::Infra::PRODUCT} executions.",
+ coerce: proc { |x| Integer(x) },
+ callbacks: { "should be a positive number" => proc { |v| v > 0 } },
+ default: 30
+
+ property :splay, [Integer, String],
+ default: 300,
+ coerce: proc { |x| Integer(x) },
+ callbacks: { "should be a positive number" => proc { |v| v > 0 } },
+ description: "A random number of seconds between 0 and X to add to interval so that all #{ChefUtils::Dist::Infra::CLIENT} commands don't execute at the same time."
+
+ property :accept_chef_license, [true, false],
+ description: "Accept the Chef Online Master License and Services Agreement. See <https://www.chef.io/online-master-agreement/>",
+ default: false
+
+ property :config_directory, String,
+ description: "The path of the config directory.",
+ default: ChefConfig::Config.etc_chef_dir
+
+ property :log_directory, String,
+ description: "The path of the directory to create the log file in.",
+ default: "/Library/Logs/Chef"
+
+ property :log_file_name, String,
+ description: "The name of the log file to use.",
+ default: "client.log"
+
+ property :chef_binary_path, String,
+ description: "The path to the #{ChefUtils::Dist::Infra::CLIENT} binary.",
+ default: "/opt/#{ChefUtils::Dist::Infra::DIR_SUFFIX}/bin/#{ChefUtils::Dist::Infra::CLIENT}"
+
+ property :daemon_options, Array,
+ description: "An array of options to pass to the #{ChefUtils::Dist::Infra::CLIENT} command.",
+ default: lazy { [] }
+
+ property :environment, Hash,
+ description: "A Hash containing additional arbitrary environment variables under which the launchd daemon will be run in the form of `({'ENV_VARIABLE' => 'VALUE'})`.",
+ default: lazy { {} }
+
+ property :nice, [Integer, String],
+ description: "The process priority to run the #{ChefUtils::Dist::Infra::CLIENT} process at. A value of -20 is the highest priority and 19 is the lowest priority.",
+ coerce: proc { |x| Integer(x) },
+ callbacks: { "should be an Integer between -20 and 19" => proc { |v| v >= -20 && v <= 19 } }
+
+ property :low_priority_io, [true, false],
+ description: "Run the #{ChefUtils::Dist::Infra::CLIENT} process with low priority disk IO",
+ default: true
+
+ action :enable do
+ unless ::Dir.exist?(new_resource.log_directory)
+ directory new_resource.log_directory do
+ owner new_resource.user
+ mode "0750"
+ recursive true
+ end
+ end
+
+ launchd "com.#{ChefUtils::Dist::Infra::SHORT}.#{ChefUtils::Dist::Infra::CLIENT}" do
+ username new_resource.user
+ working_directory new_resource.working_directory
+ start_interval new_resource.interval * 60
+ program_arguments ["/bin/bash", "-c", client_command]
+ environment_variables new_resource.environment unless new_resource.environment.empty?
+ nice new_resource.nice
+ low_priority_io true
+ notifies :sleep, "chef_sleep[Sleep before client restart]", :immediately
+ action :create # create only creates the file. No service restart triggering
+ end
+
+ # Launchd doesn't have the concept of a reload aka restart. Instead to update a daemon config you have
+ # to unload it and then reload the new plist. That's usually fine, but not if chef-client is trying
+ # to restart itself. If the chef-client process uses launchd or macosx_service resources to restart itself
+ # we'll end up with a stopped service that will never get started back up. Instead we use this daemon
+ # that triggers when the chef-client plist file is updated, and handles the restart outside the run.
+ launchd "com.#{ChefUtils::Dist::Infra::SHORT}.restarter" do
+ username "root"
+ watch_paths ["/Library/LaunchDaemons/com.#{ChefUtils::Dist::Infra::SHORT}.#{ChefUtils::Dist::Infra::CLIENT}.plist"]
+ standard_out_path ::File.join(new_resource.log_directory, new_resource.log_file_name)
+ standard_error_path ::File.join(new_resource.log_directory, new_resource.log_file_name)
+ program_arguments ["/bin/bash",
+ "-c",
+ "echo; echo #{ChefUtils::Dist::Infra::PRODUCT} launchd daemon config has been updated. Manually unloading and reloading the daemon; echo Now unloading the daemon; launchctl unload /Library/LaunchDaemons/com.#{ChefUtils::Dist::Infra::SHORT}.#{ChefUtils::Dist::Infra::CLIENT}.plist; sleep 2; echo Now loading the daemon; launchctl load /Library/LaunchDaemons/com.#{ChefUtils::Dist::Infra::SHORT}.#{ChefUtils::Dist::Infra::CLIENT}.plist"]
+ action :enable # enable creates the plist & triggers service restarts on change
+ end
+
+ # We want to make sure that after we update the chef-client launchd config that we don't move on to another recipe
+ # before the restarter daemon can do its thing. This sleep avoids killing the client while it's doing something like
+ # installing a package, which could be problematic. It also makes it a bit more clear in the log that the killed process
+ # was intentional.
+ chef_sleep "Sleep before client restart" do
+ seconds 10
+ action :nothing
+ end
+ end
+
+ action :disable do
+ service ChefUtils::Dist::Infra::PRODUCT do
+ service_name "com.#{ChefUtils::Dist::Infra::SHORT}.#{ChefUtils::Dist::Infra::CLIENT}"
+ action :disable
+ end
+
+ service "com.#{ChefUtils::Dist::Infra::SHORT}.restarter" do
+ action :disable
+ end
+ end
+
+ action_class do
+ #
+ # Generate a uniformly distributed unique number to sleep from 0 to the splay time
+ #
+ # @param [Integer] splay The number of seconds to splay
+ #
+ # @return [Integer]
+ #
+ def splay_sleep_time(splay)
+ seed = node["shard_seed"] || Digest::MD5.hexdigest(node.name).to_s.hex
+ random = Random.new(seed.to_i)
+ random.rand(splay)
+ end
+
+ #
+ # random sleep time + chef-client + daemon option properties + license acceptance
+ #
+ # @return [String]
+ #
+ def client_command
+ cmd = ""
+ cmd << "/bin/sleep #{splay_sleep_time(new_resource.splay)};"
+ cmd << " #{new_resource.chef_binary_path}"
+ cmd << " #{new_resource.daemon_options.join(" ")}" unless new_resource.daemon_options.empty?
+ cmd << " -c #{::File.join(new_resource.config_directory, "client.rb")}"
+ cmd << " -L #{::File.join(new_resource.log_directory, new_resource.log_file_name)}"
+ cmd << " --chef-license accept" if new_resource.accept_chef_license
+ cmd
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/chef_client_scheduled_task.rb b/lib/chef/resource/chef_client_scheduled_task.rb
new file mode 100644
index 0000000000..25780afdf4
--- /dev/null
+++ b/lib/chef/resource/chef_client_scheduled_task.rb
@@ -0,0 +1,216 @@
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Resource
+ class ChefClientScheduledTask < Chef::Resource
+ unified_mode true
+
+ provides :chef_client_scheduled_task
+
+ description "Use the **chef_client_scheduled_task** resource to setup the #{ChefUtils::Dist::Infra::PRODUCT} to run as a Windows scheduled task. This resource will also create the specified log directory if it doesn't already exist."
+ introduced "16.0"
+ examples <<~DOC
+ **Setup #{ChefUtils::Dist::Infra::PRODUCT} to run using the default 30 minute cadence**:
+
+ ```ruby
+ chef_client_scheduled_task 'Run #{ChefUtils::Dist::Infra::PRODUCT} as a scheduled task'
+ ```
+
+ **Run #{ChefUtils::Dist::Infra::PRODUCT} on system start**:
+
+ ```ruby
+ chef_client_scheduled_task '#{ChefUtils::Dist::Infra::PRODUCT} on start' do
+ frequency 'onstart'
+ end
+ ```
+
+ **Run #{ChefUtils::Dist::Infra::PRODUCT} with extra options passed to the client**:
+
+ ```ruby
+ chef_client_scheduled_task 'Run an override recipe' do
+ daemon_options ['--override-runlist mycorp_base::default']
+ end
+ ```
+
+ **Run #{ChefUtils::Dist::Infra::PRODUCT} daily at 01:00 am, specifying a named run-list**:
+
+ ```ruby
+ chef_client_scheduled_task 'Run chef-client named run-list daily' do
+ frequency 'daily'
+ start_time '01:00'
+ daemon_options ['-n audit_only']
+ end
+ ```
+ DOC
+
+ resource_name :chef_client_scheduled_task
+
+ property :task_name, String,
+ description: "The name of the scheduled task to create.",
+ default: ChefUtils::Dist::Infra::CLIENT
+
+ property :user, String,
+ description: "The name of the user that #{ChefUtils::Dist::Infra::PRODUCT} runs as.",
+ default: "System", sensitive: true
+
+ property :password, String,
+ description: "The password for the user that #{ChefUtils::Dist::Infra::PRODUCT} runs as.",
+ sensitive: true
+
+ property :frequency, String,
+ description: "Frequency with which to run the task.",
+ default: "minute",
+ equal_to: %w{minute hourly daily monthly once on_logon onstart on_idle}
+
+ property :frequency_modifier, [Integer, String],
+ coerce: proc { |x| Integer(x) },
+ callbacks: { "should be a positive number" => proc { |v| v > 0 } },
+ description: "Numeric value to go with the scheduled task frequency",
+ default: lazy { frequency == "minute" ? 30 : 1 },
+ default_description: "30 if frequency is 'minute', 1 otherwise"
+
+ property :accept_chef_license, [true, false],
+ description: "Accept the Chef Online Master License and Services Agreement. See <https://www.chef.io/online-master-agreement/>",
+ default: false
+
+ property :start_date, String,
+ description: "The start date for the task in m:d:Y format (ex: 12/17/2020).",
+ regex: [%r{^[0-1][0-9]\/[0-3][0-9]\/\d{4}$}]
+
+ property :start_time, String,
+ description: "The start time for the task in HH:mm format (ex: 14:00). If the frequency is minute default start time will be Time.now plus the frequency_modifier number of minutes.",
+ regex: [/^\d{2}:\d{2}$/]
+
+ property :splay, [Integer, String],
+ coerce: proc { |x| Integer(x) },
+ callbacks: { "should be a positive number" => proc { |v| v > 0 } },
+ description: "A random number of seconds between 0 and X to add to interval so that all #{ChefUtils::Dist::Infra::CLIENT} commands don't execute at the same time.",
+ default: 300
+
+ property :run_on_battery, [true, false],
+ description: "Run the #{ChefUtils::Dist::Infra::PRODUCT} task when the system is on batteries.",
+ default: true
+
+ property :config_directory, String,
+ description: "The path of the config directory.",
+ default: ChefConfig::Config.etc_chef_dir
+
+ property :log_directory, String,
+ description: "The path of the directory to create the log file in.",
+ default: lazy { |r| "#{r.config_directory}/log" },
+ default_description: "CONFIG_DIRECTORY/log"
+
+ property :log_file_name, String,
+ description: "The name of the log file to use.",
+ default: "client.log"
+
+ property :chef_binary_path, String,
+ description: "The path to the #{ChefUtils::Dist::Infra::CLIENT} binary.",
+ default: "C:/#{ChefUtils::Dist::Org::LEGACY_CONF_DIR}/#{ChefUtils::Dist::Infra::DIR_SUFFIX}/bin/#{ChefUtils::Dist::Infra::CLIENT}"
+
+ property :daemon_options, Array,
+ description: "An array of options to pass to the #{ChefUtils::Dist::Infra::CLIENT} command.",
+ default: lazy { [] }
+
+ action :add do
+ # TODO: Replace this with a :create_if_missing action on directory when that exists
+ unless Dir.exist?(new_resource.log_directory)
+ directory new_resource.log_directory do
+ inherits true
+ recursive true
+ action :create
+ end
+ end
+
+ # According to https://docs.microsoft.com/en-us/windows/desktop/taskschd/schtasks,
+ # the :once, :onstart, :onlogon, and :onidle schedules don't accept schedule modifiers
+
+ windows_task new_resource.task_name do
+ run_level :highest
+ command full_command
+ user new_resource.user
+ password new_resource.password
+ frequency new_resource.frequency.to_sym
+ frequency_modifier new_resource.frequency_modifier if frequency_supports_frequency_modifier?
+ start_time new_resource.start_time
+ start_day new_resource.start_date unless new_resource.start_date.nil?
+ random_delay new_resource.splay if frequency_supports_random_delay?
+ disallow_start_if_on_batteries new_resource.splay unless new_resource.run_on_battery
+ action %i{create enable}
+ end
+ end
+
+ action :remove do
+ windows_task new_resource.task_name do
+ action :delete
+ end
+ end
+
+ action_class do
+ #
+ # The full command to run in the scheduled task
+ #
+ # @return [String]
+ #
+ def full_command
+ # Fetch path of cmd.exe through environment variable comspec
+ cmd_path = ENV["COMSPEC"]
+
+ "#{cmd_path} /c \"#{client_cmd}\""
+ end
+
+ #
+ # Build command line to pass to cmd.exe
+ #
+ # @return [String]
+ #
+ def client_cmd
+ cmd = new_resource.chef_binary_path.dup
+ cmd << " -L #{::File.join(new_resource.log_directory, new_resource.log_file_name)}"
+ cmd << " -c #{::File.join(new_resource.config_directory, "client.rb")}"
+
+ # Add custom options
+ cmd << " #{new_resource.daemon_options.join(" ")}" if new_resource.daemon_options.any?
+ cmd << " --chef-license accept" if new_resource.accept_chef_license
+ cmd
+ end
+
+ #
+ # not all frequencies in the windows_task resource support random_delay
+ #
+ # @return [boolean]
+ #
+ def frequency_supports_random_delay?
+ %w{once minute hourly daily weekly monthly}.include?(new_resource.frequency)
+ end
+
+ #
+ # not all frequencies in the windows_task resource support frequency_modifier
+ #
+ # @return [boolean]
+ #
+ def frequency_supports_frequency_modifier?
+ # these are the only ones that don't
+ !%w{once on_logon onstart on_idle}.include?(new_resource.frequency)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/chef_client_systemd_timer.rb b/lib/chef/resource/chef_client_systemd_timer.rb
new file mode 100644
index 0000000000..e911fc2cb0
--- /dev/null
+++ b/lib/chef/resource/chef_client_systemd_timer.rb
@@ -0,0 +1,187 @@
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Resource
+ class ChefClientSystemdTimer < Chef::Resource
+ unified_mode true
+
+ provides :chef_client_systemd_timer
+
+ description "Use the **chef_client_systemd_timer** resource to setup the #{ChefUtils::Dist::Infra::PRODUCT} to run as a systemd timer."
+ introduced "16.0"
+ examples <<~DOC
+ **Setup #{ChefUtils::Dist::Infra::PRODUCT} to run using the default 30 minute cadence**:
+
+ ```ruby
+ chef_client_systemd_timer 'Run #{ChefUtils::Dist::Infra::PRODUCT} as a systemd timer'
+ ```
+
+ **Run #{ChefUtils::Dist::Infra::PRODUCT} every 1 hour**:
+
+ ```ruby
+ chef_client_systemd_timer 'Run #{ChefUtils::Dist::Infra::PRODUCT} every 1 hour' do
+ interval '1hr'
+ end
+ ```
+
+ **Run #{ChefUtils::Dist::Infra::PRODUCT} with extra options passed to the client**:
+
+ ```ruby
+ chef_client_systemd_timer 'Run an override recipe' do
+ daemon_options ['--override-runlist mycorp_base::default']
+ end
+ ```
+ DOC
+
+ property :job_name, String,
+ description: "The name of the system timer to create.",
+ default: ChefUtils::Dist::Infra::CLIENT
+
+ property :description, String,
+ description: "The description to add to the systemd timer. This will be displayed when running `systemctl status` for the timer.",
+ default: "#{ChefUtils::Dist::Infra::PRODUCT} periodic execution"
+
+ property :user, String,
+ description: "The name of the user that #{ChefUtils::Dist::Infra::PRODUCT} runs as.",
+ default: "root"
+
+ property :delay_after_boot, String,
+ description: "The time to wait after booting before the interval starts. This is expressed as a systemd time span such as `300seconds`, `1hr`, or `1m`. See <https://www.freedesktop.org/software/systemd/man/systemd.time.html> for a complete list of allowed time span values.",
+ default: "1min"
+
+ property :interval, String,
+ description: "The interval to wait between executions. This is expressed as a systemd time span such as `300seconds`, `1hr`, or `1m`. See <https://www.freedesktop.org/software/systemd/man/systemd.time.html> for a complete list of allowed time span values.",
+ default: "30min"
+
+ property :splay, String,
+ description: "A interval between 0 and X to add to the interval so that all #{ChefUtils::Dist::Infra::CLIENT} commands don't execute at the same time. This is expressed as a systemd time span such as `300seconds`, `1hr`, or `1m`. See <https://www.freedesktop.org/software/systemd/man/systemd.time.html> for a complete list of allowed time span values.",
+ default: "5min"
+
+ property :accept_chef_license, [true, false],
+ description: "Accept the Chef Online Master License and Services Agreement. See <https://www.chef.io/online-master-agreement/>",
+ default: false
+
+ property :run_on_battery, [true, false],
+ description: "Run the timer for #{ChefUtils::Dist::Infra::PRODUCT} if the system is on battery.",
+ default: true
+
+ property :config_directory, String,
+ description: "The path of the config directory.",
+ default: ChefConfig::Config.etc_chef_dir
+
+ property :chef_binary_path, String,
+ description: "The path to the #{ChefUtils::Dist::Infra::CLIENT} binary.",
+ default: "/opt/#{ChefUtils::Dist::Infra::DIR_SUFFIX}/bin/#{ChefUtils::Dist::Infra::CLIENT}"
+
+ property :daemon_options, Array,
+ description: "An array of options to pass to the #{ChefUtils::Dist::Infra::CLIENT} command.",
+ default: lazy { [] }
+
+ property :environment, Hash,
+ description: "A Hash containing additional arbitrary environment variables under which the systemd timer will be run in the form of `({'ENV_VARIABLE' => 'VALUE'})`.",
+ default: lazy { {} }
+
+ property :cpu_quota, [Integer, String],
+ description: "The systemd CPUQuota to run the #{ChefUtils::Dist::Infra::CLIENT} process with. This is a percentage value of the total CPU time available on the system. If the system has more than 1 core this may be a value greater than 100.",
+ introduced: "16.5",
+ coerce: proc { |x| Integer(x) },
+ callbacks: { "should be a positive Integer" => proc { |v| v > 0 } }
+
+ action :add do
+ systemd_unit "#{new_resource.job_name}.service" do
+ content service_content
+ action :create
+ end
+
+ systemd_unit "#{new_resource.job_name}.timer" do
+ content timer_content
+ action %i{create enable start}
+ end
+ end
+
+ action :remove do
+ systemd_unit "#{new_resource.job_name}.service" do
+ action :delete
+ end
+
+ systemd_unit "#{new_resource.job_name}.timer" do
+ action :delete
+ end
+ end
+
+ action_class do
+ #
+ # The chef-client command to run in the systemd unit.
+ #
+ # @return [String]
+ #
+ def chef_client_cmd
+ cmd = new_resource.chef_binary_path.dup
+ cmd << " #{new_resource.daemon_options.join(" ")}" unless new_resource.daemon_options.empty?
+ cmd << " --chef-license accept" if new_resource.accept_chef_license
+ cmd << " -c #{::File.join(new_resource.config_directory, "client.rb")}"
+ cmd
+ end
+
+ #
+ # The timer content to pass to the systemd_unit
+ #
+ # @return [Hash]
+ #
+ def timer_content
+ {
+ "Unit" => { "Description" => new_resource.description },
+ "Timer" => {
+ "OnBootSec" => new_resource.delay_after_boot,
+ "OnUnitActiveSec" => new_resource.interval,
+ "RandomizedDelaySec" => new_resource.splay,
+ },
+ "Install" => { "WantedBy" => "timers.target" },
+ }
+ end
+
+ #
+ # The service content to pass to the systemd_unit
+ #
+ # @return [Hash]
+ #
+ def service_content
+ unit = {
+ "Unit" => {
+ "Description" => new_resource.description,
+ "After" => "network.target auditd.service",
+ },
+ "Service" => {
+ "Type" => "oneshot",
+ "ExecStart" => chef_client_cmd,
+ "SuccessExitStatus" => [3, 213, 35, 37, 41],
+ },
+ "Install" => { "WantedBy" => "multi-user.target" },
+ }
+
+ unit["Service"]["ConditionACPower"] = "true" unless new_resource.run_on_battery
+ unit["Service"]["CPUQuota"] = new_resource.cpu_quota if new_resource.cpu_quota
+ unit["Service"]["Environment"] = new_resource.environment.collect { |k, v| "\"#{k}=#{v}\"" } unless new_resource.environment.empty?
+ unit
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/chef_client_trusted_certificate.rb b/lib/chef/resource/chef_client_trusted_certificate.rb
new file mode 100644
index 0000000000..b5272fbe01
--- /dev/null
+++ b/lib/chef/resource/chef_client_trusted_certificate.rb
@@ -0,0 +1,101 @@
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Resource
+ class ChefClientTrustedCertificate < Chef::Resource
+ unified_mode true
+
+ provides :chef_client_trusted_certificate
+
+ description "Use the **chef_client_trusted_certificate** resource to add certificates to #{ChefUtils::Dist::Infra::PRODUCT}'s trusted certificate directory. This allows the #{ChefUtils::Dist::Infra::PRODUCT} to communicate with internal encrypted resources without errors."
+ introduced "16.5"
+ examples <<~DOC
+ **Trust a self signed certificate**:
+
+ ```ruby
+ chef_client_trusted_certificate 'self-signed.badssl.com' do
+ certificate <<~CERT
+ -----BEGIN CERTIFICATE-----
+ MIIDeTCCAmGgAwIBAgIJAPziuikCTox4MA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNV
+ BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp
+ c2NvMQ8wDQYDVQQKDAZCYWRTU0wxFTATBgNVBAMMDCouYmFkc3NsLmNvbTAeFw0x
+ OTEwMDkyMzQxNTJaFw0yMTEwMDgyMzQxNTJaMGIxCzAJBgNVBAYTAlVTMRMwEQYD
+ VQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ8wDQYDVQQK
+ DAZCYWRTU0wxFTATBgNVBAMMDCouYmFkc3NsLmNvbTCCASIwDQYJKoZIhvcNAQEB
+ BQADggEPADCCAQoCggEBAMIE7PiM7gTCs9hQ1XBYzJMY61yoaEmwIrX5lZ6xKyx2
+ PmzAS2BMTOqytMAPgLaw+XLJhgL5XEFdEyt/ccRLvOmULlA3pmccYYz2QULFRtMW
+ hyefdOsKnRFSJiFzbIRMeVXk0WvoBj1IFVKtsyjbqv9u/2CVSndrOfEk0TG23U3A
+ xPxTuW1CrbV8/q71FdIzSOciccfCFHpsKOo3St/qbLVytH5aohbcabFXRNsKEqve
+ ww9HdFxBIuGa+RuT5q0iBikusbpJHAwnnqP7i/dAcgCskgjZjFeEU4EFy+b+a1SY
+ QCeFxxC7c3DvaRhBB0VVfPlkPz0sw6l865MaTIbRyoUCAwEAAaMyMDAwCQYDVR0T
+ BAIwADAjBgNVHREEHDAaggwqLmJhZHNzbC5jb22CCmJhZHNzbC5jb20wDQYJKoZI
+ hvcNAQELBQADggEBAGlwCdbPxflZfYOaukZGCaxYK6gpincX4Lla4Ui2WdeQxE95
+ w7fChXvP3YkE3UYUE7mupZ0eg4ZILr/A0e7JQDsgIu/SRTUE0domCKgPZ8v99k3A
+ vka4LpLK51jHJJK7EFgo3ca2nldd97GM0MU41xHFk8qaK1tWJkfrrfcGwDJ4GQPI
+ iLlm6i0yHq1Qg1RypAXJy5dTlRXlCLd8ufWhhiwW0W75Va5AEnJuqpQrKwl3KQVe
+ wGj67WWRgLfSr+4QG1mNvCZb2CkjZWmxkGPuoP40/y7Yu5OFqxP5tAjj4YixCYTW
+ EVA0pmzIzgBg+JIe3PdRy27T0asgQW/F4TY61Yk=
+ -----END CERTIFICATE-----
+ CERT
+ end
+ ```
+ DOC
+
+ property :cert_name, String, name_property: true,
+ description: "The name to use for the certificate file on disk. If not provided the name of the resource block will be used instead."
+
+ property :certificate, String, required: [:add],
+ description: "The text of the certificate file including the BEGIN/END comment lines."
+
+ action :add do
+ unless ::Dir.exist?(Chef::Config[:trusted_certs_dir])
+ directory Chef::Config[:trusted_certs_dir] do
+ mode "0640"
+ recursive true
+ end
+ end
+
+ file cert_path do
+ content new_resource.certificate
+ mode "0640"
+ end
+ end
+
+ action :remove do
+ file cert_path do
+ action :delete
+ end
+ end
+
+ action_class do
+ #
+ # The path to the string on disk
+ #
+ # @return [String]
+ #
+ def cert_path
+ path = ::File.join(Chef::Config[:trusted_certs_dir], new_resource.cert_name)
+ path << ".pem" unless path.end_with?(".pem")
+ path
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/chef_gem.rb b/lib/chef/resource/chef_gem.rb
index 4445bf0f89..2c5b342bce 100644
--- a/lib/chef/resource/chef_gem.rb
+++ b/lib/chef/resource/chef_gem.rb
@@ -1,6 +1,6 @@
#
# Author:: Bryan McLellan <btm@loftninjas.org>
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,36 +16,80 @@
# limitations under the License.
#
-require "chef/resource/package"
-require "chef/resource/gem_package"
+require_relative "package"
+require_relative "gem_package"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Resource
class ChefGem < Chef::Resource::Package::GemPackage
- resource_name :chef_gem
-
- 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 { |v| v == "#{RbConfig::CONFIG['bindir']}/gem" },
- }
- property :compile_time, [ true, false, nil ], 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."
+ unified_mode true
+ provides :chef_gem
+
+ description <<~DESC
+ Use the **chef_gem** resource to install a gem only for the instance of Ruby that is dedicated to the #{ChefUtils::Dist::Infra::CLIENT}.
+ When a gem is installed from a local file, it must be added to the node using the **remote_file** or **cookbook_file** resources.
+
+ The **chef_gem** resource works with all of the same properties and options as the **gem_package** resource, but does not
+ accept the `gem_binary` property because it always uses the `CurrentGemEnvironment` under which the `#{ChefUtils::Dist::Infra::CLIENT}` is
+ running. In addition to performing actions similar to the **gem_package** resource, the **chef_gem** resource does the
+ following:
+ - Runs its actions immediately, before convergence, allowing a gem to be used in a recipe immediately after it is installed.
+ - Runs `Gem.clear_paths` after the action, ensuring that gem is aware of changes so that it can be required immediately after it is installed.
+
+ Warning: The **chef_gem** and **gem_package** resources are both used to install Ruby gems. For any machine on which #{ChefUtils::Dist::Infra::PRODUCT} is
+ installed, there are two instances of Ruby. One is the standard, system-wide instance of Ruby and the other is a dedicated instance that is
+ available only to #{ChefUtils::Dist::Infra::PRODUCT}.
+ Use the **chef_gem** resource to install gems into the instance of Ruby that is dedicated to #{ChefUtils::Dist::Infra::PRODUCT}.
+ Use the **gem_package** resource to install all other gems (i.e. install gems system-wide).
+ DESC
+
+ examples <<~EXAMPLES
+ **Compile time vs. converge time installation of gems**
+
+ To install a gem while #{ChefUtils::Dist::Infra::PRODUCT} is configuring the node (the converge phase), set the `compile_time` property to `false`:
+ ```ruby
+ chef_gem 'loofah' do
+ compile_time false
+ action :install
+ end
+ ```
+
+ To install a gem while the resource collection is being built (the compile phase), set the `compile_time` property to `true`:
+ ```ruby
+ chef_gem 'loofah' do
+ compile_time true
+ action :install
end
+ ```
+
+ **Install MySQL gem into #{ChefUtils::Dist::Infra::PRODUCT}***
+ ```ruby
+ apt_update
- if compile_time || compile_time.nil?
- Array(action).each do |action|
- self.run_action(action)
- end
- Gem.clear_paths
+ build_essential 'install compilation tools' do
+ compile_time true
end
- end
+
+ chef_gem 'mysql'
+ ```
+ EXAMPLES
+
+ property :package_name, String,
+ description: "An optional property to set the package name if it differs from the resource block's name.",
+ identity: true
+
+ property :version, String,
+ description: "The version of a package to be installed or upgraded."
+
+ property :gem_binary, String,
+ default: "#{RbConfig::CONFIG["bindir"]}/gem",
+ default_description: "The `gem` binary included with #{ChefUtils::Dist::Infra::PRODUCT}.",
+ description: "The path of a gem binary to use for the installation. By default, the same version of Ruby that is used by #{ChefUtils::Dist::Infra::PRODUCT} will be used.",
+ callbacks: {
+ "The `chef_gem` resource is restricted to the current gem environment, use `gem_package` to install to other environments." =>
+ proc { |v| v == "#{RbConfig::CONFIG["bindir"]}/gem" },
+ }
end
end
end
diff --git a/lib/chef/resource/chef_handler.rb b/lib/chef/resource/chef_handler.rb
new file mode 100644
index 0000000000..a006b2648a
--- /dev/null
+++ b/lib/chef/resource/chef_handler.rb
@@ -0,0 +1,283 @@
+#
+# Author:: Seth Chisamore <schisamo@chef.io>
+# Copyright:: Copyright (c) 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_relative "../resource"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Resource
+ class ChefHandler < Chef::Resource
+ unified_mode true
+
+ provides(:chef_handler) { true }
+
+ description "Use the **chef_handler** resource to enable handlers during a #{ChefUtils::Dist::Infra::PRODUCT} run. The resource allows arguments to be passed to #{ChefUtils::Dist::Infra::PRODUCT}, which then applies the conditions defined by the custom handler to the node attribute data collected during a #{ChefUtils::Dist::Infra::PRODUCT} run, and then processes the handler based on that data.\nThe **chef_handler** resource is typically defined early in a node's run-list (often being the first item). This ensures that all of the handlers will be available for the entire #{ChefUtils::Dist::Infra::PRODUCT} run."
+ introduced "14.0"
+ examples <<~'DOC'
+ **Enable the 'MyHandler' handler**
+
+ The following example shows how to enable a fictional 'MyHandler' handler which is located on disk at `/etc/chef/my_handler.rb`. The handler will be configured to run with Chef Infra Client and will be passed values to the handler's initializer method:
+
+ ```ruby
+ chef_handler 'MyHandler' do
+ source '/etc/chef/my_handler.rb' # the file should already be at this path
+ arguments path: '/var/chef/reports'
+ action :enable
+ end
+ ```
+
+ **Enable handlers during the compile phase**
+
+ ```ruby
+ chef_handler 'Chef::Handler::JsonFile' do
+ source 'chef/handler/json_file'
+ arguments path: '/var/chef/reports'
+ action :enable
+ compile_time true
+ end
+ ```
+
+ **Handle only exceptions**
+
+ ```ruby
+ chef_handler 'Chef::Handler::JsonFile' do
+ source 'chef/handler/json_file'
+ arguments path: '/var/chef/reports'
+ type exception: true
+ action :enable
+ end
+ ```
+
+ **Cookbook Versions (a custom handler)**
+
+ [@juliandunn](https://github.com/juliandunn) created a custom report handler that logs all of the cookbooks and cookbook versions that were used during a Chef Infra Client run, and then reports after the run is complete.
+
+ cookbook_versions.rb:
+
+ The following custom handler defines how cookbooks and cookbook versions that are used during a Chef Infra Client run will be compiled into a report using the `Chef::Log` class in Chef Infra Client:
+
+ ```ruby
+ require 'chef/log'
+
+ module Chef
+ class CookbookVersionsHandler < Chef::Handler
+ def report
+ cookbooks = run_context.cookbook_collection
+ Chef::Log.info('Cookbooks and versions run: #{cookbooks.map {|x| x.name.to_s + ' ' + x.version }}')
+ end
+ end
+ end
+ ```
+
+ default.rb:
+
+ The following recipe is added to the run-list for every node on which a list of cookbooks and versions will be generated as report output after every Chef Infra Client run.
+
+ ```ruby
+ cookbook_file '/etc/chef/cookbook_versions.rb' do
+ source 'cookbook_versions.rb'
+ action :create
+ end
+
+ chef_handler 'Chef::CookbookVersionsHandler' do
+ source '/etc/chef/cookbook_versions.rb'
+ type report: true
+ action :enable
+ end
+ ```
+
+ This recipe will generate report output similar to the following:
+
+ ```
+ [2013-11-26T03:11:06+00:00] INFO: Chef Infra Client Run complete in 0.300029878 seconds
+ [2013-11-26T03:11:06+00:00] INFO: Running report handlers
+ [2013-11-26T03:11:06+00:00] INFO: Cookbooks and versions run: ["cookbook_versions_handler 1.0.0"]
+ [2013-11-26T03:11:06+00:00] INFO: Report handlers complete
+ ```
+
+ **JsonFile Handler**
+
+ The JsonFile handler is available from the `chef_handler` cookbook and can be used with exceptions and reports. It serializes run status data to a JSON file. This handler may be enabled in one of the following ways.
+
+ By adding the following lines of Ruby code to either the client.rb file or the solo.rb file, depending on how Chef Infra Client is being run:
+
+ ```ruby
+ require 'chef/handler/json_file'
+ report_handlers << Chef::Handler::JsonFile.new(path: '/var/chef/reports')
+ exception_handlers << Chef::Handler::JsonFile.new(path: '/var/chef/reports')
+ ```
+
+ By using the `chef_handler` resource in a recipe, similar to the following:
+
+ ```ruby
+ chef_handler 'Chef::Handler::JsonFile' do
+ source 'chef/handler/json_file'
+ arguments path: '/var/chef/reports'
+ action :enable
+ end
+ ```
+
+ After it has run, the run status data can be loaded and inspected via Interactive Ruby (IRb):
+
+ ```
+ irb(main):002:0> require 'json' => true
+ irb(main):003:0> require 'chef' => true
+ irb(main):004:0> r = JSON.parse(IO.read('/var/chef/reports/chef-run-report-20110322060731.json')) => ... output truncated
+ irb(main):005:0> r.keys => ['end_time', 'node', 'updated_resources', 'exception', 'all_resources', 'success', 'elapsed_time', 'start_time', 'backtrace']
+ irb(main):006:0> r['elapsed_time'] => 0.00246
+ ```
+
+ Register the JsonFile handler
+
+ ```ruby
+ chef_handler 'Chef::Handler::JsonFile' do
+ source 'chef/handler/json_file'
+ arguments path: '/var/chef/reports'
+ action :enable
+ end
+ ```
+
+ **ErrorReport Handler**
+
+ The ErrorReport handler is built into Chef Infra Client and can be used for both exceptions and reports. It serializes error report data to a JSON file. This handler may be enabled in one of the following ways.
+
+ By adding the following lines of Ruby code to either the client.rb file or the solo.rb file, depending on how Chef Infra Client is being run:
+
+ ```ruby
+ require 'chef/handler/error_report'
+ report_handlers << Chef::Handler::ErrorReport.new
+ exception_handlers << Chef::Handler::ErrorReport.new
+ ```
+
+ By using the `chef_handler` resource in a recipe, similar to the following:
+
+ ```ruby
+ chef_handler 'Chef::Handler::ErrorReport' do
+ source 'chef/handler/error_report'
+ action :enable
+ end
+ ```
+ DOC
+
+ property :class_name, String,
+ description: "The name of the handler class. This can be module name-spaced.",
+ name_property: true
+
+ property :source, String,
+ description: "The full path to the handler file. Can also be a gem path if the handler ships as part of a Ruby gem."
+
+ property :arguments, [Array, Hash],
+ description: "Arguments to pass the handler's class initializer.",
+ default: lazy { [] }
+
+ property :type, Hash,
+ description: "The type of handler to register as, i.e. :report, :exception or both.",
+ default: { report: true, exception: true }
+
+ # supports means a different thing in chef-land so we renamed it but
+ # wanted to make sure we didn't break the world
+ alias_method :supports, :type
+
+ # This action needs to find an rb file that presumably contains the indicated class in it and the
+ # load that file. It then instantiates that class by name and registers it as a handler.
+ action :enable do
+ description "Enables the handler for the current #{ChefUtils::Dist::Infra::PRODUCT} run on the current node"
+
+ class_name = new_resource.class_name
+ new_resource.type.each do |type, enable|
+ next unless enable
+
+ unregister_handler(type, class_name)
+ end
+
+ handler = nil
+
+ require new_resource.source unless new_resource.source.nil?
+
+ _, klass = get_class(class_name)
+ handler = klass.send(:new, *collect_args(new_resource.arguments))
+
+ new_resource.type.each do |type, enable|
+ next unless enable
+
+ register_handler(type, handler)
+ end
+ end
+
+ action :disable do
+ description "Disables the handler for the current #{ChefUtils::Dist::Infra::PRODUCT} run on the current node"
+
+ new_resource.type.each_key do |type|
+ unregister_handler(type, new_resource.class_name)
+ end
+ end
+
+ action_class do
+ # Registers a handler in Chef::Config.
+ #
+ # @param handler_type [Symbol] such as :report or :exception.
+ # @param handler [Chef::Handler] handler to register.
+ def register_handler(handler_type, handler)
+ Chef::Log.info("Enabling #{handler.class.name} as a #{handler_type} handler.")
+ Chef::Config.send("#{handler_type}_handlers") << handler
+ end
+
+ # Removes all handlers that match the given class name in Chef::Config.
+ #
+ # @param handler_type [Symbol] such as :report or :exception.
+ # @param class_full_name [String] such as 'Chef::Handler::ErrorReport'.
+ #
+ # @return [void]
+ def unregister_handler(handler_type, class_full_name)
+ Chef::Config.send("#{handler_type}_handlers").delete_if do |v|
+ # avoid a bit of log spam
+ if v.class.name == class_full_name
+ Chef::Log.info("Disabling #{class_full_name} as a #{handler_type} handler.")
+ true
+ end
+ end
+ end
+
+ # Walks down the namespace hierarchy to return the class object for the given class name.
+ # If the class is not available, NameError is thrown.
+ #
+ # @param class_full_name [String] full class name such as 'Chef::Handler::Foo' or 'MyHandler'.
+ #
+ # @return [Array] parent class and child class.
+ def get_class(class_full_name)
+ ancestors = class_full_name.split("::")
+ class_name = ancestors.pop
+
+ # We need to search the ancestors only for the first/uppermost namespace of the class, so we
+ # need to enable the #const_get inherit parameter only when we are searching in Kernel scope
+ # (see COOK-4117).
+ parent = ancestors.inject(Kernel) { |scope, const_name| scope.const_get(const_name, scope === Kernel) }
+ child = parent.const_get(class_name, parent === Kernel)
+ [parent, child]
+ end
+
+ def collect_args(resource_args = [])
+ if resource_args.is_a? Array
+ resource_args
+ else
+ [resource_args]
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/chef_sleep.rb b/lib/chef/resource/chef_sleep.rb
new file mode 100644
index 0000000000..c6d3e45877
--- /dev/null
+++ b/lib/chef/resource/chef_sleep.rb
@@ -0,0 +1,72 @@
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Resource
+ class ChefSleep < Chef::Resource
+ provides :chef_sleep
+
+ unified_mode true
+
+ description "Use the **chef_sleep** resource to pause (sleep) for a number of seconds during a #{ChefUtils::Dist::Infra::PRODUCT} run. Only use this resource when a command or service exits successfully but is not ready for the next step in a recipe."
+ introduced "15.5"
+ examples <<~DOC
+ **Sleep for 10 seconds**:
+
+ ```ruby
+ chef_sleep '10'
+ ```
+
+ **Sleep for 10 seconds with a descriptive resource name for logging**:
+
+ ```ruby
+ chef_sleep 'wait for the service to start' do
+ seconds 10
+ end
+ ```
+
+ **Use a notification from another resource to sleep only when necessary**:
+
+ ```ruby
+ service 'Service that is slow to start and reports as started' do
+ service_name 'my_database'
+ action :start
+ notifies :sleep, chef_sleep['wait for service start']
+ end
+
+ chef_sleep 'wait for service start' do
+ seconds 30
+ action :nothing
+ end
+ ```
+ DOC
+
+ property :seconds, [String, Integer],
+ description: "The number of seconds to sleep.",
+ coerce: proc { |s| Integer(s) },
+ name_property: true
+
+ action :sleep do
+ converge_by("sleep #{new_resource.seconds} seconds") do
+ sleep(new_resource.seconds)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/chef_vault_secret.rb b/lib/chef/resource/chef_vault_secret.rb
new file mode 100644
index 0000000000..1c8fa985f9
--- /dev/null
+++ b/lib/chef/resource/chef_vault_secret.rb
@@ -0,0 +1,135 @@
+#
+# Author:: Joshua Timberman <joshua@chef.io>
+# Copyright:: Copyright (c) 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_relative "../resource"
+autoload :ChefVault, "chef-vault"
+
+class Chef
+ class Resource
+ class ChefVaultSecret < Chef::Resource
+ unified_mode true
+
+ provides :chef_vault_secret
+
+ introduced "16.0"
+ description "Use the **chef_vault_secret** resource to store secrets in Chef Vault items. Where possible and relevant, this resource attempts to map behavior and functionality to the knife vault sub-commands."
+ examples <<~DOC
+ **To create a 'foo' item in an existing 'bar' data bag**:
+
+ ```ruby
+ chef_vault_secret 'foo' do
+ data_bag 'bar'
+ raw_data({'auth' => 'baz'})
+ admins 'jtimberman'
+ search '*:*'
+ end
+ ```
+
+ **To allow multiple admins access to an item**:
+
+ ```ruby
+ chef_vault_secret 'root-password' do
+ admins 'jtimberman,paulmooring'
+ data_bag 'secrets'
+ raw_data({'auth' => 'DoNotUseThisPasswordForRoot'})
+ search '*:*'
+ end
+ ```
+ DOC
+
+ property :id, String, name_property: true,
+ description: "The name of the data bag item if it differs from the name of the resource block"
+
+ property :data_bag, String, required: true, desired_state: false,
+ description: "The data bag that contains the item."
+
+ property :admins, [String, Array], required: true, desired_state: false,
+ description: "A list of admin users who should have access to the item. Corresponds to the 'admin' option when using the chef-vault knife plugin. Can be specified as a comma separated string or an array."
+
+ property :clients, [String, Array], desired_state: false,
+ description: "A search query for the nodes' API clients that should have access to the item."
+
+ property :search, String, default: "*:*", desired_state: false,
+ description: "Search query that would match the same used for the clients, gets stored as a field in the item."
+
+ property :raw_data, [Hash, Mash], default: {},
+ description: "The raw data, as a Ruby Hash, that will be stored in the item."
+
+ property :environment, [String, NilClass], desired_state: false,
+ description: "The Chef environment of the data if storing per environment values."
+
+ load_current_value do
+
+ item = ChefVault::Item.load(data_bag, id)
+ raw_data item.raw_data
+ clients item.get_clients
+ admins item.get_admins
+ search item.search
+ rescue ChefVault::Exceptions::SecretDecryption
+ current_value_does_not_exist!
+ rescue ChefVault::Exceptions::KeysNotFound
+ current_value_does_not_exist!
+ rescue Net::HTTPClientException => e
+ current_value_does_not_exist! if e.response_code == "404"
+
+ end
+
+ action :create do
+ description "Creates the item, or updates it if it already exists."
+
+ converge_if_changed do
+ item = ChefVault::Item.new(new_resource.data_bag, new_resource.id)
+
+ Chef::Log.debug("#{new_resource.id} environment: '#{new_resource.environment}'")
+ item.raw_data = if new_resource.environment.nil?
+ new_resource.raw_data.merge("id" => new_resource.id)
+ else
+ { "id" => new_resource.id, new_resource.environment => new_resource.raw_data }
+ end
+
+ Chef::Log.debug("#{new_resource.id} search query: '#{new_resource.search}'")
+ item.search(new_resource.search)
+ Chef::Log.debug("#{new_resource.id} clients: '#{new_resource.clients}'")
+ item.clients([new_resource.clients].flatten.join(",")) unless new_resource.clients.nil?
+ Chef::Log.debug("#{new_resource.id} admins (users): '#{new_resource.admins}'")
+ item.admins([new_resource.admins].flatten.join(","))
+ item.save
+ end
+ end
+
+ action :create_if_missing do
+ description "Calls the create action unless it exists."
+
+ action_create if current_resource.nil?
+ end
+
+ action :delete do
+ description "Deletes the item and the item's keys ('id'_keys)."
+
+ chef_data_bag_item new_resource.id do
+ data_bag new_resource.data_bag
+ action :delete
+ end
+
+ chef_data_bag_item [new_resource.id, "keys"].join("_") do
+ data_bag new_resource.data_bag
+ action :delete
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/chocolatey_config.rb b/lib/chef/resource/chocolatey_config.rb
new file mode 100644
index 0000000000..c4f100c28d
--- /dev/null
+++ b/lib/chef/resource/chocolatey_config.rb
@@ -0,0 +1,102 @@
+#
+# Copyright:: Copyright (c) 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.
+#
+
+class Chef
+ class Resource
+ class ChocolateyConfig < Chef::Resource
+ unified_mode true
+
+ provides :chocolatey_config
+
+ description "Use the **chocolatey_config** resource to add or remove Chocolatey configuration keys."
+ introduced "14.3"
+ examples <<~DOC
+ **Set the Chocolatey cacheLocation config**:
+
+ ```ruby
+ chocolatey_config 'Set cacheLocation config' do
+ config_key 'cacheLocation'
+ value 'C:\temp\choco'
+ end
+ ```
+
+ **Unset a Chocolatey config**:
+
+ ```ruby
+ chocolatey_config 'BogusConfig' do
+ action :unset
+ end
+ ```
+ DOC
+
+ property :config_key, String, name_property: true,
+ description: "An optional property to set the config key name if it differs from the resource block's name."
+
+ property :value, String,
+ description: "The value to set."
+
+ load_current_value do
+ current_val = fetch_config_element(config_key)
+ current_value_does_not_exist! if current_val.nil?
+
+ config_key config_key
+ value current_val
+ end
+
+ # @param [String] id the config name
+ # @return [String] the element's value field
+ def fetch_config_element(id)
+ require "rexml/document" unless defined?(REXML::Document)
+ config_file = "#{ENV["ALLUSERSPROFILE"]}\\chocolatey\\config\\chocolatey.config"
+ raise "Could not find the Chocolatey config at #{config_file}!" unless ::File.exist?(config_file)
+
+ contents = REXML::Document.new(::File.read(config_file))
+ data = REXML::XPath.first(contents, "//config/add[@key=\"#{id}\"]")
+ data ? data.attribute("value").to_s : nil # REXML just returns nil if it can't find anything so avoid an undefined method error
+ end
+
+ action :set do
+ description "Sets a Chocolatey config value."
+
+ raise "#{new_resource}: When adding a Chocolatey config you must pass the 'value' property!" unless new_resource.value
+
+ converge_if_changed do
+ shell_out!(choco_cmd("set"))
+ end
+ end
+
+ action :unset do
+ description "Unsets a Chocolatey config value."
+
+ if current_resource
+ converge_by("unset Chocolatey config '#{new_resource.config_key}'") do
+ shell_out!(choco_cmd("unset"))
+ end
+ end
+ end
+
+ action_class do
+ # @param [String] action the name of the action to perform
+ # @return [String] the choco config command string
+ def choco_cmd(action)
+ cmd = "#{ENV["ALLUSERSPROFILE"]}\\chocolatey\\bin\\choco config #{action} --name #{new_resource.config_key}"
+ cmd << " --value #{new_resource.value}" if action == "set"
+ cmd
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/chocolatey_feature.rb b/lib/chef/resource/chocolatey_feature.rb
new file mode 100644
index 0000000000..66752fbd5d
--- /dev/null
+++ b/lib/chef/resource/chocolatey_feature.rb
@@ -0,0 +1,97 @@
+#
+# Copyright:: Copyright (c) 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.
+#
+
+class Chef
+ class Resource
+ class ChocolateyFeature < Chef::Resource
+ unified_mode true
+ provides :chocolatey_feature
+
+ description "Use the **chocolatey_feature** resource to enable and disable Chocolatey features."
+ introduced "15.1"
+ examples <<~DOC
+ **Enable the checksumFiles Chocolatey feature**
+
+ ```ruby
+ chocolatey_feature 'checksumFiles' do
+ action :enable
+ end
+ ```
+
+ **Disable the checksumFiles Chocolatey feature**
+
+ ```ruby
+ chocolatey_feature 'checksumFiles' do
+ action :disable
+ end
+ ```
+ DOC
+
+ property :feature_name, String, name_property: true,
+ description: "The name of the Chocolatey feature to enable or disable."
+
+ property :feature_state, [TrueClass, FalseClass], default: false, skip_docs: true
+
+ load_current_value do
+ current_state = fetch_feature_element(feature_name)
+ current_value_does_not_exist! if current_state.nil?
+
+ feature_name feature_name
+ feature_state current_state == "true"
+ end
+
+ # @param [String] id the feature name
+ # @return [String] the element's value field
+ def fetch_feature_element(name)
+ require "rexml/document" unless defined?(REXML::Document)
+ config_file = "#{ENV["ALLUSERSPROFILE"]}\\chocolatey\\config\\chocolatey.config"
+ raise "Could not find the Chocolatey config at #{config_file}!" unless ::File.exist?(config_file)
+
+ contents = REXML::Document.new(::File.read(config_file))
+ data = REXML::XPath.first(contents, "//features/feature[@name=\"#{name}\"]")
+ data ? data.attribute("enabled").to_s : nil # REXML just returns nil if it can't find anything so avoid an undefined method error
+ end
+
+ action :enable do
+ description "Enables a named Chocolatey feature."
+
+ if current_resource.feature_state != true
+ converge_by("enable Chocolatey feature '#{new_resource.feature_name}'") do
+ shell_out!(choco_cmd("enable"))
+ end
+ end
+ end
+
+ action :disable do
+ description "Disables a named Chocolatey feature."
+
+ if current_resource.feature_state == true
+ converge_by("disable Chocolatey feature '#{new_resource.feature_name}'") do
+ shell_out!(choco_cmd("disable"))
+ end
+ end
+ end
+
+ action_class do
+ # @param [String] action the name of the action to perform
+ # @return [String] the choco feature command string
+ def choco_cmd(action)
+ "#{ENV["ALLUSERSPROFILE"]}\\chocolatey\\bin\\choco feature #{action} --name #{new_resource.feature_name}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/chocolatey_package.rb b/lib/chef/resource/chocolatey_package.rb
index 805d3a3121..e0568e586a 100644
--- a/lib/chef/resource/chocolatey_package.rb
+++ b/lib/chef/resource/chocolatey_package.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,24 +16,68 @@
# limitations under the License.
#
-require "chef/resource/package"
+require_relative "package"
class Chef
class Resource
class ChocolateyPackage < Chef::Resource::Package
+ unified_mode true
- provides :chocolatey_package, os: "windows"
+ provides :chocolatey_package
- allowed_actions :install, :upgrade, :remove, :uninstall, :purge, :reconfig
+ description "Use the **chocolatey_package** resource to manage packages using Chocolatey on the Microsoft Windows platform. Note: The Chocolatey package manager is not installed on Windows by default. You will need to install it prior to using this resource by adding the [Chocolatey cookbook](https://supermarket.chef.io/cookbooks/chocolatey/) to your node's run list."
+ introduced "12.7"
+ examples <<~DOC
+ **Install a Chocolatey package**:
- def initialize(name, run_context = nil)
- super
- @resource_name = :chocolatey_package
- end
+ ```ruby
+ chocolatey_package 'name of package' do
+ action :install
+ end
+ ```
- property :package_name, [String, Array], coerce: proc { |x| [x].flatten }
+ **Install a package with options with Chocolatey's `--checksum` option**:
- property :version, [String, Array], coerce: proc { |x| [x].flatten }
+ ```ruby
+ chocolatey_package 'name of package' do
+ options '--checksum 1234567890'
+ action :install
+ end
+ ```
+ DOC
+
+ allowed_actions :install, :upgrade, :remove, :purge, :reconfig
+
+ # windows can't take Array options yet
+ property :options, [String, Array],
+ description: "One (or more) additional options that are passed to the command."
+
+ property :list_options, String,
+ introduced: "15.3",
+ description: "One (or more) additional list options that are passed to the command."
+
+ property :user, String,
+ introduced: "15.3",
+ description: "The username to authenticate feeds."
+
+ property :password, String,
+ introduced: "15.3",
+ description: "The password to authenticate to the source."
+
+ property :package_name, [String, Array],
+ description: "The name of the package. Default value: the name of the resource block.",
+ coerce: proc { |x| [x].flatten }
+
+ property :version, [String, Array],
+ description: "The version of a package to be installed or upgraded.",
+ coerce: proc { |x| [x].flatten }
+
+ # In the choco if we have the feature useEnhancedExitCodes turned on, then choco will provide enhanced exit codes(2: no results).
+ # Choco exit codes https://chocolatey.org/docs/commandsinfo#exit-codes
+ property :returns, [Integer, Array],
+ description: "The exit code(s) returned a chocolatey package that indicate success.",
+ default: [ 0, 2 ], desired_state: false,
+ introduced: "12.18"
end
end
end
diff --git a/lib/chef/resource/chocolatey_source.rb b/lib/chef/resource/chocolatey_source.rb
new file mode 100644
index 0000000000..cee4289682
--- /dev/null
+++ b/lib/chef/resource/chocolatey_source.rb
@@ -0,0 +1,148 @@
+#
+# Copyright:: Copyright (c) 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.
+#
+
+class Chef
+ class Resource
+ class ChocolateySource < Chef::Resource
+ unified_mode true
+ provides :chocolatey_source
+
+ description "Use the **chocolatey_source** resource to add, remove, enable, or disable Chocolatey sources."
+ introduced "14.3"
+ examples <<~DOC
+ **Add a Chocolatey source**
+
+ ```ruby
+ chocolatey_source 'MySource' do
+ source 'http://example.com/something'
+ action :add
+ end
+ ```
+
+ **Remove a Chocolatey source**
+
+ ```ruby
+ chocolatey_source 'MySource' do
+ action :remove
+ end
+ ```
+ DOC
+
+ property :source_name, String, name_property: true,
+ description: "An optional property to set the source name if it differs from the resource block's name."
+
+ property :source, String,
+ description: "The source URL."
+
+ property :bypass_proxy, [TrueClass, FalseClass], default: false,
+ description: "Whether or not to bypass the system's proxy settings to access the source."
+
+ property :admin_only, [TrueClass, FalseClass], default: false,
+ description: "Whether or not to set the source to be accessible to only admins.",
+ introduced: "15.1"
+
+ property :allow_self_service, [TrueClass, FalseClass], default: false,
+ description: "Whether or not to set the source to be used for self service.",
+ introduced: "15.1"
+
+ property :priority, Integer, default: 0,
+ description: "The priority level of the source."
+
+ property :disabled, [TrueClass, FalseClass], default: false, desired_state: false, skip_docs: true
+
+ load_current_value do
+ element = fetch_source_element(source_name)
+ current_value_does_not_exist! if element.nil?
+
+ source_name element["id"]
+ source element["value"]
+ bypass_proxy element["bypassProxy"] == "true"
+ admin_only element["adminOnly"] == "true"
+ allow_self_service element["selfService"] == "true"
+ priority element["priority"].to_i
+ disabled element["disabled"] == "true"
+ end
+
+ # @param [String] id the source name
+ # @return [REXML::Attributes] finds the source element with the
+ def fetch_source_element(id)
+ require "rexml/document" unless defined?(REXML::Document)
+
+ config_file = "#{ENV["ALLUSERSPROFILE"]}\\chocolatey\\config\\chocolatey.config"
+ raise "Could not find the Chocolatey config at #{config_file}!" unless ::File.exist?(config_file)
+
+ config_contents = REXML::Document.new(::File.read(config_file))
+ data = REXML::XPath.first(config_contents, "//sources/source[@id=\"#{id}\"]")
+ data ? data.attributes : nil # REXML just returns nil if it can't find anything so avoid an undefined method error
+ end
+
+ action :add do
+ description "Adds a Chocolatey source."
+
+ raise "#{new_resource}: When adding a Chocolatey source you must pass the 'source' property!" unless new_resource.source
+
+ converge_if_changed do
+ shell_out!(choco_cmd("add"))
+ end
+ end
+
+ action :remove do
+ description "Removes a Chocolatey source."
+
+ if current_resource
+ converge_by("remove Chocolatey source '#{new_resource.source_name}'") do
+ shell_out!(choco_cmd("remove"))
+ end
+ end
+ end
+
+ action :disable do
+ description "Disables a Chocolatey source."
+
+ if current_resource.disabled != true
+ converge_by("disable Chocolatey source '#{new_resource.source_name}'") do
+ shell_out!(choco_cmd("disable"))
+ end
+ end
+ end
+
+ action :enable do
+ description "Enables a Chocolatey source."
+
+ if current_resource.disabled == true
+ converge_by("enable Chocolatey source '#{new_resource.source_name}'") do
+ shell_out!(choco_cmd("enable"))
+ end
+ end
+ end
+
+ action_class do
+ # @param [String] action the name of the action to perform
+ # @return [String] the choco source command string
+ def choco_cmd(action)
+ cmd = "#{ENV["ALLUSERSPROFILE"]}\\chocolatey\\bin\\choco source #{action} -n \"#{new_resource.source_name}\""
+ if action == "add"
+ cmd << " -s #{new_resource.source} --priority=#{new_resource.priority}"
+ cmd << " --bypassproxy" if new_resource.bypass_proxy
+ cmd << " --allowselfservice" if new_resource.allow_self_service
+ cmd << " --adminonly" if new_resource.admin_only
+ end
+ cmd
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/conditional.rb b/lib/chef/resource/conditional.rb
index 452718cae8..76f7637693 100644
--- a/lib/chef/resource/conditional.rb
+++ b/lib/chef/resource/conditional.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/shell_out"
-require "chef/guard_interpreter"
+require_relative "../mixin/shell_out"
+require_relative "../guard_interpreter"
class Chef
class Resource
diff --git a/lib/chef/resource/cookbook_file.rb b/lib/chef/resource/cookbook_file.rb
index 785cf693be..f1ae195426 100644
--- a/lib/chef/resource/cookbook_file.rb
+++ b/lib/chef/resource/cookbook_file.rb
@@ -2,7 +2,7 @@
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Seth Chisamore (<schisamo@chef.io>)
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,32 +18,31 @@
# limitations under the License.
#
-require "chef/resource/file"
-require "chef/provider/cookbook_file"
-require "chef/mixin/securable"
+require_relative "file"
+require_relative "../provider/cookbook_file"
+require_relative "../mixin/securable"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Resource
class CookbookFile < Chef::Resource::File
include Chef::Mixin::Securable
+ unified_mode true
- default_action :create
+ provides :cookbook_file
- def initialize(name, run_context = nil)
- super
- @provider = Chef::Provider::CookbookFile
- @source = ::File.basename(name)
- @cookbook = nil
- end
+ description "Use the **cookbook_file** resource to transfer files from a sub-directory of COOKBOOK_NAME/files/ to a specified path located on a host that is running the #{ChefUtils::Dist::Infra::PRODUCT}. The file is selected according to file specificity, which allows different source files to be used based on the hostname, host platform (operating system, distro, or as appropriate), or platform version. Files that are located in the COOKBOOK_NAME/files/default sub-directory may be used on any platform.\n\nDuring a #{ChefUtils::Dist::Infra::PRODUCT} run, the checksum for each local file is calculated and then compared against the checksum for the same file as it currently exists in the cookbook on the #{ChefUtils::Dist::Server::PRODUCT}. A file is not transferred when the checksums match. Only files that require an update are transferred from the #{ChefUtils::Dist::Server::PRODUCT} to a node."
- def source(source_filename = nil)
- set_or_return(:source, source_filename, :kind_of => [ String, Array ])
- end
+ property :source, [ String, Array ],
+ description: "The name of the file in COOKBOOK_NAME/files/default or the path to a file located in COOKBOOK_NAME/files. The path must include the file name and its extension. This can be used to distribute specific files depending upon the platform used.",
+ default: lazy { ::File.basename(name) }
- def cookbook(cookbook_name = nil)
- set_or_return(:cookbook, cookbook_name, :kind_of => String)
- end
+ property :cookbook, String,
+ description: "The cookbook in which a file is located (if it is not located in the current cookbook).",
+ desired_state: false,
+ default_description: "The current cookbook name"
+ default_action :create
end
end
end
diff --git a/lib/chef/resource/cron.rb b/lib/chef/resource/cron.rb
deleted file mode 100644
index a76d454bf0..0000000000
--- a/lib/chef/resource/cron.rb
+++ /dev/null
@@ -1,216 +0,0 @@
-#
-# Author:: Bryan McLellan (btm@loftninjas.org)
-# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2009-2016, Bryan McLellan
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require "chef/resource"
-
-class Chef
- class Resource
- class Cron < Chef::Resource
-
- identity_attr :command
-
- state_attrs :minute, :hour, :day, :month, :weekday, :user
-
- default_action :create
- allowed_actions :create, :delete
-
- def initialize(name, run_context = nil)
- super
- @minute = "*"
- @hour = "*"
- @day = "*"
- @month = "*"
- @weekday = "*"
- @command = nil
- @user = "root"
- @mailto = nil
- @path = nil
- @shell = nil
- @home = nil
- @time = nil
- @environment = {}
- end
-
- def minute(arg = nil)
- if arg.is_a?(Integer)
- converted_arg = arg.to_s
- else
- converted_arg = arg
- end
- begin
- if integerize(arg) > 59 then raise RangeError end
- rescue ArgumentError
- end
- set_or_return(
- :minute,
- converted_arg,
- :kind_of => String
- )
- end
-
- def hour(arg = nil)
- if arg.is_a?(Integer)
- converted_arg = arg.to_s
- else
- converted_arg = arg
- end
- begin
- if integerize(arg) > 23 then raise RangeError end
- rescue ArgumentError
- end
- set_or_return(
- :hour,
- converted_arg,
- :kind_of => String
- )
- end
-
- def day(arg = nil)
- if arg.is_a?(Integer)
- converted_arg = arg.to_s
- else
- converted_arg = arg
- end
- begin
- if integerize(arg) > 31 then raise RangeError end
- rescue ArgumentError
- end
- set_or_return(
- :day,
- converted_arg,
- :kind_of => String
- )
- end
-
- def month(arg = nil)
- if arg.is_a?(Integer)
- converted_arg = arg.to_s
- else
- converted_arg = arg
- end
- begin
- if integerize(arg) > 12 then raise RangeError end
- rescue ArgumentError
- end
- set_or_return(
- :month,
- converted_arg,
- :kind_of => String
- )
- end
-
- def weekday(arg = nil)
- if arg.is_a?(Integer)
- converted_arg = arg.to_s
- else
- converted_arg = arg
- end
- begin
- error_message = "You provided '#{arg}' as a weekday, acceptable values are "
- 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) ||
- (!arg.is_a?(Symbol) && integerize(arg) < 0)
- raise RangeError, error_message
- end
- rescue ArgumentError
- end
- set_or_return(
- :weekday,
- converted_arg,
- :kind_of => [String, Symbol]
- )
- end
-
- def time(arg = nil)
- set_or_return(
- :time,
- arg,
- :equal_to => Chef::Provider::Cron::SPECIAL_TIME_VALUES
- )
- end
-
- def mailto(arg = nil)
- set_or_return(
- :mailto,
- arg,
- :kind_of => String
- )
- end
-
- def path(arg = nil)
- set_or_return(
- :path,
- arg,
- :kind_of => String
- )
- end
-
- def home(arg = nil)
- set_or_return(
- :home,
- arg,
- :kind_of => String
- )
- end
-
- def shell(arg = nil)
- set_or_return(
- :shell,
- arg,
- :kind_of => String
- )
- end
-
- def command(arg = nil)
- set_or_return(
- :command,
- arg,
- :kind_of => String
- )
- end
-
- def user(arg = nil)
- set_or_return(
- :user,
- arg,
- :kind_of => String
- )
- end
-
- def environment(arg = nil)
- set_or_return(
- :environment,
- arg,
- :kind_of => Hash
- )
- end
-
- private
-
- # On Ruby 1.8, Kernel#Integer will happily do this for you. On 1.9, no.
- def integerize(integerish)
- Integer(integerish)
- rescue TypeError
- 0
- end
- end
- end
-end
diff --git a/lib/chef/resource/cron/_cron_shared.rb b/lib/chef/resource/cron/_cron_shared.rb
new file mode 100644
index 0000000000..6d11035862
--- /dev/null
+++ b/lib/chef/resource/cron/_cron_shared.rb
@@ -0,0 +1,99 @@
+unified_mode true
+
+TIMEOUT_OPTS = %w{duration preserve-status foreground kill-after signal}.freeze
+TIMEOUT_REGEX = /\A\S+/.freeze
+WEEKDAYS = {
+ sunday: "0", monday: "1", tuesday: "2", wednesday: "3", thursday: "4", friday: "5", saturday: "6",
+ sun: "0", mon: "1", tue: "2", wed: "3", thu: "4", fri: "5", sat: "6"
+}.freeze
+
+property :minute, [Integer, String],
+ description: "The minute at which the cron entry should run (`0 - 59`).",
+ default: "*", callbacks: {
+ "should be a valid minute spec" => ->(spec) { Chef::ResourceHelpers::CronValidations.validate_numeric(spec, 0, 59) },
+ }
+
+property :hour, [Integer, String],
+ description: "The hour at which the cron entry is to run (`0 - 23`).",
+ default: "*", callbacks: {
+ "should be a valid hour spec" => ->(spec) { Chef::ResourceHelpers::CronValidations.validate_numeric(spec, 0, 23) },
+ }
+
+property :day, [Integer, String],
+ description: "The day of month at which the cron entry should run (`1 - 31`).",
+ default: "*", callbacks: {
+ "should be a valid day spec" => ->(spec) { Chef::ResourceHelpers::CronValidations.validate_numeric(spec, 1, 31) },
+ }
+
+property :month, [Integer, String],
+ description: "The month in the year on which a cron entry is to run (`1 - 12`, `jan-dec`, or `*`).",
+ default: "*", callbacks: {
+ "should be a valid month spec" => ->(spec) { Chef::ResourceHelpers::CronValidations.validate_month(spec) },
+ }
+
+property :weekday, [Integer, String, Symbol],
+ description: "The day of the week on which this entry is to run (`0-7`, `mon-sun`, `monday-sunday`, or `*`), where Sunday is both `0` and `7`.",
+ default: "*", coerce: proc { |day| weekday_in_crontab(day) },
+ callbacks: {
+ "should be a valid weekday spec" => ->(spec) { Chef::ResourceHelpers::CronValidations.validate_dow(spec) },
+ }
+
+property :shell, String,
+ description: "Set the `SHELL` environment variable."
+
+property :path, String,
+ description: "Set the `PATH` environment variable."
+
+property :home, String,
+ description: "Set the `HOME` environment variable."
+
+property :mailto, String,
+ description: "Set the `MAILTO` environment variable."
+
+property :command, String,
+ description: "The command to be run, or the path to a file that contains the command to be run.",
+ identity: true,
+ required: [:create]
+
+property :user, String,
+ description: "The name of the user that runs the command.",
+ default: "root"
+
+property :environment, Hash,
+ description: "A Hash containing additional arbitrary environment variables under which the cron job will be run in the form of `({'ENV_VARIABLE' => 'VALUE'})`. **Note**: These variables must exist for a command to be run successfully.",
+ default: lazy { {} }
+
+property :time_out, Hash,
+ description: "A Hash of timeouts in the form of `({'OPTION' => 'VALUE'})`. Accepted valid options are:
+ - `preserve-status` (BOOL, default: 'false'),
+ - `foreground` (BOOL, default: 'false'),
+ - `kill-after` (in seconds),
+ - `signal` (a name like 'HUP' or a number)",
+ default: lazy { {} },
+ introduced: "15.7",
+ coerce: proc { |h|
+ if h.is_a?(Hash)
+ invalid_keys = h.keys - TIMEOUT_OPTS
+ unless invalid_keys.empty?
+ error_msg = "Key of option time_out must be equal to one of: \"#{TIMEOUT_OPTS.join('", "')}\"! You passed \"#{invalid_keys.join(", ")}\"."
+ raise Chef::Exceptions::ValidationFailed, error_msg
+ end
+ unless h.values.all? { |x| x =~ TIMEOUT_REGEX }
+ error_msg = "Values of option time_out should be non-empty strings without any leading whitespace."
+ raise Chef::Exceptions::ValidationFailed, error_msg
+ end
+ h
+ elsif h.is_a?(Integer) || h.is_a?(String)
+ { "duration" => h }
+ end
+ }
+
+private
+
+# Convert weekday input value into crontab format that
+# could be written in the crontab
+# @return [Integer, String] A weekday formed as per the user inputs.
+def weekday_in_crontab(day)
+ weekday = day.to_s.downcase.to_sym
+ WEEKDAYS[weekday] || day
+end
diff --git a/lib/chef/resource/cron/cron.rb b/lib/chef/resource/cron/cron.rb
new file mode 100644
index 0000000000..31d6efcfde
--- /dev/null
+++ b/lib/chef/resource/cron/cron.rb
@@ -0,0 +1,46 @@
+#
+# Author:: Bryan McLellan (btm@loftninjas.org)
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright 2009-2016, Bryan McLellan
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../../resource"
+require_relative "../helpers/cron_validations"
+require_relative "../../provider/cron" # do not remove. we actually need this below
+
+class Chef
+ class Resource
+ class Cron < Chef::Resource
+ unified_mode true
+
+ use "cron_shared"
+
+ provides :cron
+
+ description "Use the **cron** resource to manage cron entries for time-based job scheduling. Properties for a schedule will default to * if not provided. The cron resource requires access to a crontab program, typically cron."
+
+ state_attrs :minute, :hour, :day, :month, :weekday, :user
+
+ default_action :create
+ allowed_actions :create, :delete
+
+ property :time, Symbol,
+ description: "A time interval.",
+ equal_to: Chef::Provider::Cron::SPECIAL_TIME_VALUES
+
+ end
+ end
+end
diff --git a/lib/chef/resource/cron/cron_d.rb b/lib/chef/resource/cron/cron_d.rb
new file mode 100644
index 0000000000..7fea6ac76d
--- /dev/null
+++ b/lib/chef/resource/cron/cron_d.rb
@@ -0,0 +1,188 @@
+#
+# Copyright:: Copyright (c) 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_relative "../../resource"
+require_relative "../helpers/cron_validations"
+require "shellwords" unless defined?(Shellwords)
+
+class Chef
+ class Resource
+ class CronD < Chef::Resource
+ unified_mode true
+
+ use "cron_shared"
+
+ provides :cron_d
+
+ introduced "14.4"
+ description "Use the **cron_d** resource to manage cron job files in the /etc/cron.d directory. This is similar to the 'cron' resource, but it does not use the monolithic /etc/crontab file."
+ examples <<~DOC
+ **Run a program on the fifth hour of the day**
+
+ ```ruby
+ cron_d 'noop' do
+ hour '5'
+ minute '0'
+ command '/bin/true'
+ end
+ ```
+
+ **Run an entry if a folder exists**
+
+ ```ruby
+ cron_d 'ganglia_tomcat_thread_max' do
+ command "/usr/bin/gmetric
+ -n 'tomcat threads max'
+ -t uint32
+ -v '/usr/local/bin/tomcat-stat
+ --thread-max'"
+ only_if { ::File.exist?('/home/jboss') }
+ end
+ ```
+
+ **Run an entry every Saturday, 8:00 AM**
+
+ ```ruby
+ cron_d 'name_of_cron_entry' do
+ minute '0'
+ hour '8'
+ weekday '6'
+ mailto 'admin@example.com'
+ command '/bin/true'
+ action :create
+ end
+ ```
+
+ **Run an entry at 8:00 PM, every weekday (Monday through Friday), but only in November**
+
+ ```ruby
+ cron_d 'name_of_cron_entry' do
+ minute '0'
+ hour '20'
+ day '*'
+ month '11'
+ weekday '1-5'
+ command '/bin/true'
+ action :create
+ end
+ ```
+
+ **Remove a cron job by name**:
+
+ ```ruby
+ cron_d 'job_to_remove' do
+ action :delete
+ end
+ ```
+ DOC
+
+ property :cron_name, String,
+ description: "An optional property to set the cron name if it differs from the resource block's name.",
+ name_property: true
+
+ property :cookbook, String, desired_state: false, skip_docs: true
+
+ property :predefined_value, String,
+ description: "Schedule your cron job with one of the special predefined value instead of ** * pattern.",
+ equal_to: %w{ @reboot @yearly @annually @monthly @weekly @daily @midnight @hourly }
+
+ property :comment, String,
+ description: "A comment to place in the cron.d file."
+
+ property :mode, [String, Integer],
+ description: "The octal mode of the generated crontab file.",
+ default: "0600"
+
+ property :random_delay, Integer,
+ description: "Set the `RANDOM_DELAY` environment variable in the cron.d file."
+
+ # warn if someone passes the deprecated cookbook property
+ def after_created
+ raise ArgumentError, "The 'cookbook' property for the cron_d resource is no longer supported now that it ships as a core resource." if cookbook
+ end
+
+ action :create do
+ description "Add a cron definition file to /etc/cron.d."
+
+ create_template(:create)
+ end
+
+ action :create_if_missing do
+ description "Add a cron definition file to /etc/cron.d, but do not update an existing file."
+
+ create_template(:create_if_missing)
+ end
+
+ action :delete do
+ description "Remove a cron definition file from /etc/cron.d if it exists."
+
+ # cleanup the legacy named job if it exists
+ file "legacy named cron.d file" do
+ path "/etc/cron.d/#{new_resource.cron_name}"
+ action :delete
+ end
+
+ file "/etc/cron.d/#{sanitized_name}" do
+ action :delete
+ end
+ end
+
+ action_class do
+ # @return [String] cron_name property with . replaced with -
+ def sanitized_name
+ new_resource.cron_name.tr(".", "-")
+ end
+
+ def create_template(create_action)
+ # cleanup the legacy named job if it exists
+ file "#{new_resource.cron_name} legacy named cron.d file" do
+ path "/etc/cron.d/#{new_resource.cron_name}"
+ action :delete
+ only_if { new_resource.cron_name != sanitized_name }
+ end
+
+ # @todo this is Chef 12 era cleanup. Someday we should remove it all
+ template "/etc/cron.d/#{sanitized_name}" do
+ source ::File.expand_path("../support/cron.d.erb", __dir__)
+ local true
+ mode new_resource.mode
+ sensitive new_resource.sensitive
+ variables(
+ name: sanitized_name,
+ predefined_value: new_resource.predefined_value,
+ minute: new_resource.minute,
+ hour: new_resource.hour,
+ day: new_resource.day,
+ month: new_resource.month,
+ weekday: new_resource.weekday,
+ command: new_resource.command,
+ user: new_resource.user,
+ mailto: new_resource.mailto,
+ path: new_resource.path,
+ home: new_resource.home,
+ shell: new_resource.shell,
+ comment: new_resource.comment,
+ random_delay: new_resource.random_delay,
+ environment: new_resource.environment
+ )
+ action create_action
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/cron_access.rb b/lib/chef/resource/cron_access.rb
new file mode 100644
index 0000000000..3ea777ce9c
--- /dev/null
+++ b/lib/chef/resource/cron_access.rb
@@ -0,0 +1,102 @@
+#
+# Author:: Sander Botman <sbotman@schubergphilis.com>
+# Author:: Tim Smith <tsmith@chef.io>
+#
+# Copyright:: 2014-2018, Sander Botman
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class CronAccess < Chef::Resource
+ unified_mode true
+ provides :cron_access
+ provides(:cron_manage) # legacy name @todo in Chef 15 we should { true } this so it wins over the cookbook
+
+ introduced "14.4"
+ description "Use the **cron_access** resource to manage cron's cron.allow and cron.deny files. Note: This resource previously shipped in the `cron` cookbook as `cron_manage`, which it can still be used as for backwards compatibility with existing Chef Infra Client releases."
+ examples <<~DOC
+ **Add the mike user to cron.allow**
+
+ ```ruby
+ cron_access 'mike'
+ ```
+
+ **Add the mike user to cron.deny**
+
+ ```ruby
+ cron_access 'mike' do
+ action :deny
+ end
+ ```
+
+ **Specify the username with the user property**
+
+ ```ruby
+ cron_access 'Deny the jenkins user access to cron for security purposes' do
+ user 'jenkins'
+ action :deny
+ end
+ ```
+ DOC
+
+ property :user, String,
+ description: "An optional property to set the user name if it differs from the resource block's name.",
+ name_property: true
+
+ CRON_PATHS = {
+ "aix" => "/var/adm/cron",
+ "solaris" => "/etc/cron.d",
+ "default" => "/etc",
+ }.freeze
+
+ action :allow do
+ description "Add the user to the cron.allow file."
+ allow_path = ::File.join(value_for_platform_family(CRON_PATHS), "cron.allow")
+
+ with_run_context :root do
+ edit_resource(:template, allow_path) do |new_resource|
+ source ::File.expand_path("support/cron_access.erb", __dir__)
+ local true
+ mode "0600"
+ variables["users"] ||= []
+ variables["users"] << new_resource.user
+ action :nothing
+ delayed_action :create
+ end
+ end
+ end
+
+ action :deny do
+ description "Add the user to the cron.deny file."
+ deny_path = ::File.join(value_for_platform_family(CRON_PATHS), "cron.deny")
+
+ with_run_context :root do
+ edit_resource(:template, deny_path) do |new_resource|
+ source ::File.expand_path("support/cron_access.erb", __dir__)
+ local true
+ mode "0600"
+ variables["users"] ||= []
+ variables["users"] << new_resource.user
+ action :nothing
+ delayed_action :create
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/csh.rb b/lib/chef/resource/csh.rb
index 4e7c22b660..6f0c37d01e 100644
--- a/lib/chef/resource/csh.rb
+++ b/lib/chef/resource/csh.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,12 +16,21 @@
# limitations under the License.
#
-require "chef/resource/script"
-require "chef/provider/script"
+require_relative "script"
class Chef
class Resource
class Csh < Chef::Resource::Script
+ unified_mode true
+
+ provides :csh
+
+ description "Use the **csh** resource to execute scripts using the csh interpreter."\
+ " This resource may also use any of the actions and properties that are"\
+ " available to the **execute** resource. Commands that are executed with this"\
+ " resource are (by their nature) not idempotent, as they are typically"\
+ " unique to the environment in which they are run. Use `not_if` and `only_if`"\
+ " to guard this resource for idempotence."
def initialize(name, run_context = nil)
super
diff --git a/lib/chef/resource/deploy.rb b/lib/chef/resource/deploy.rb
deleted file mode 100644
index b8e6a26f97..0000000000
--- a/lib/chef/resource/deploy.rb
+++ /dev/null
@@ -1,443 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@kallistec.com>)
-# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2008-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.
-#
-
-# EX:
-# deploy "/my/deploy/dir" do
-# repo "git@github.com/whoami/project"
-# revision "abc123" # or "HEAD" or "TAG_for_1.0" or (subversion) "1234"
-# user "deploy_ninja"
-# enable_submodules true
-# migrate true
-# 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"
-# scm_provider Chef::Provider::Git # is the default, for svn: Chef::Provider::Subversion
-# svn_username "whoami"
-# svn_password "supersecret"
-# end
-
-require "chef/resource/scm"
-
-class Chef
- class Resource
-
- # Deploy: Deploy apps from a source control repository.
- #
- # Callbacks:
- # Callbacks can be a block or a string. If given a block, the code
- # is evaluated as an embedded recipe, and run at the specified
- # point in the deploy process. If given a string, the string is taken as
- # a path to a callback file/recipe. Paths are evaluated relative to the
- # release directory. Callback files can contain chef code (resources, etc.)
- #
- class Deploy < Chef::Resource
-
- identity_attr :repository
-
- state_attrs :deploy_to, :revision
-
- default_action :deploy
- allowed_actions :force_deploy, :deploy, :rollback
-
- def initialize(name, run_context = nil)
- super
- @deploy_to = name
- @environment = nil
- @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"
- @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
- @additional_remotes = Hash[]
- @keep_releases = 5
- @enable_checkout = true
- @checkout_branch = "deploy"
- end
-
- # where the checked out/cloned code goes
- def destination
- @destination ||= shared_path + "/#{@repository_cache}"
- end
-
- # where shared stuff goes, i.e., logs, tmp, etc. goes here
- def shared_path
- @shared_path ||= @deploy_to + "/shared"
- end
-
- # where the deployed version of your code goes
- def current_path
- @current_path ||= @deploy_to + "/current"
- end
-
- def depth(arg = @shallow_clone ? 5 : nil)
- set_or_return(
- :depth,
- arg,
- :kind_of => [ Integer ]
- )
- end
-
- # note: deploy_to is your application "meta-root."
- def deploy_to(arg = nil)
- set_or_return(
- :deploy_to,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def repo(arg = nil)
- set_or_return(
- :repo,
- arg,
- :kind_of => [ String ]
- )
- end
- alias :repository :repo
-
- def remote(arg = nil)
- set_or_return(
- :remote,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def role(arg = nil)
- set_or_return(
- :role,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def restart_command(arg = nil, &block)
- arg ||= block
- set_or_return(
- :restart_command,
- arg,
- :kind_of => [ String, Proc ]
- )
- end
- alias :restart :restart_command
-
- def migrate(arg = nil)
- set_or_return(
- :migrate,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
-
- def migration_command(arg = nil)
- set_or_return(
- :migration_command,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def rollback_on_error(arg = nil)
- set_or_return(
- :rollback_on_error,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
-
- def user(arg = nil)
- set_or_return(
- :user,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def group(arg = nil)
- set_or_return(
- :group,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def enable_submodules(arg = nil)
- set_or_return(
- :enable_submodules,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
-
- def shallow_clone(arg = nil)
- set_or_return(
- :shallow_clone,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
-
- def repository_cache(arg = nil)
- set_or_return(
- :repository_cache,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def copy_exclude(arg = nil)
- set_or_return(
- :copy_exclude,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def revision(arg = nil)
- set_or_return(
- :revision,
- arg,
- :kind_of => [ String ]
- )
- end
- alias :branch :revision
-
- def git_ssh_wrapper(arg = nil)
- set_or_return(
- :git_ssh_wrapper,
- arg,
- :kind_of => [ String ]
- )
- end
- alias :ssh_wrapper :git_ssh_wrapper
-
- def svn_username(arg = nil)
- set_or_return(
- :svn_username,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def svn_password(arg = nil)
- set_or_return(
- :svn_password,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def svn_arguments(arg = nil)
- set_or_return(
- :svn_arguments,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def svn_info_args(arg = nil)
- set_or_return(
- :svn_arguments,
- arg,
- :kind_of => [ String ])
- end
-
- def scm_provider(arg = nil)
- klass = if arg.kind_of?(String) || arg.kind_of?(Symbol)
- lookup_provider_constant(arg)
- else
- arg
- end
- set_or_return(
- :scm_provider,
- klass,
- :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 ]
- )
- end
-
- def environment(arg = nil)
- if arg.is_a?(String)
- Chef::Log.debug "Setting RAILS_ENV, RACK_ENV, and MERB_ENV to `#{arg}'"
- Chef::Log.warn "[DEPRECATED] please modify your deploy recipe or attributes to set the environment using a hash"
- arg = { "RAILS_ENV" => arg, "MERB_ENV" => arg, "RACK_ENV" => arg }
- end
- set_or_return(
- :environment,
- arg,
- :kind_of => [ Hash ]
- )
- end
-
- # The number of old release directories to keep around after cleanup
- def keep_releases(arg = nil)
- [set_or_return(
- :keep_releases,
- arg,
- :kind_of => [ Integer ]), 1].max
- end
-
- # An array of paths, relative to your app's root, to be purged from a
- # SCM clone/checkout before symlinking. Use this to get rid of files and
- # directories you want to be shared between releases.
- # Default: ["log", "tmp/pids", "public/system"]
- def purge_before_symlink(arg = nil)
- set_or_return(
- :purge_before_symlink,
- arg,
- :kind_of => Array
- )
- end
-
- # An array of paths, relative to your app's root, where you expect dirs to
- # exist before symlinking. This runs after #purge_before_symlink, so you
- # can use this to recreate dirs that you had previously purged.
- # For example, if you plan to use a shared directory for pids, and you
- # want it to be located in $APP_ROOT/tmp/pids, you could purge tmp,
- # then specify tmp here so that the tmp directory will exist when you
- # symlink the pids directory in to the current release.
- # Default: ["tmp", "public", "config"]
- def create_dirs_before_symlink(arg = nil)
- set_or_return(
- :create_dirs_before_symlink,
- arg,
- :kind_of => Array
- )
- end
-
- # A Hash of shared/dir/path => release/dir/path. This attribute determines
- # which files and dirs in the shared directory get symlinked to the current
- # release directory, and where they go. If you have a directory
- # $shared/pids that you would like to symlink as $current_release/tmp/pids
- # you specify it as "pids" => "tmp/pids"
- # Default {"system" => "public/system", "pids" => "tmp/pids", "log" => "log"}
- def symlinks(arg = nil)
- set_or_return(
- :symlinks,
- arg,
- :kind_of => Hash
- )
- end
-
- # A Hash of shared/dir/path => release/dir/path. This attribute determines
- # which files in the shared directory get symlinked to the current release
- # directory and where they go. Unlike map_shared_files, these are symlinked
- # *before* any migration is run.
- # For a rails/merb app, this is used to link in a known good database.yml
- # (with the production db password) before running migrate.
- # Default {"config/database.yml" => "config/database.yml"}
- def symlink_before_migrate(arg = nil)
- set_or_return(
- :symlink_before_migrate,
- arg,
- :kind_of => Hash
- )
- end
-
- # Callback fires before migration is run.
- def before_migrate(arg = nil, &block)
- arg ||= block
- set_or_return(:before_migrate, arg, :kind_of => [Proc, String])
- end
-
- # Callback fires before symlinking
- def before_symlink(arg = nil, &block)
- arg ||= block
- set_or_return(:before_symlink, arg, :kind_of => [Proc, String])
- end
-
- # Callback fires before restart
- def before_restart(arg = nil, &block)
- arg ||= block
- set_or_return(:before_restart, arg, :kind_of => [Proc, String])
- end
-
- # Callback fires after restart
- def after_restart(arg = nil, &block)
- arg ||= block
- set_or_return(:after_restart, arg, :kind_of => [Proc, String])
- end
-
- def additional_remotes(arg = nil)
- set_or_return(
- :additional_remotes,
- arg,
- :kind_of => Hash
- )
- end
-
- def enable_checkout(arg = nil)
- set_or_return(
- :enable_checkout,
- arg,
- :kind_of => [TrueClass, FalseClass]
- )
- end
-
- def checkout_branch(arg = nil)
- set_or_return(
- :checkout_branch,
- arg,
- :kind_of => String
- )
- end
-
- # FIXME The Deploy resource may be passed to an SCM provider as its
- # resource. The SCM provider knows that SCM resources can specify a
- # timeout for SCM operations. The deploy resource must therefore support
- # a timeout method, but the timeout it describes is for SCM operations,
- # not the overall deployment. This is potentially confusing.
- def timeout(arg = nil)
- set_or_return(
- :timeout,
- arg,
- :kind_of => Integer
- )
- end
-
- end
- end
-end
diff --git a/lib/chef/resource/directory.rb b/lib/chef/resource/directory.rb
index faad659668..d51fb144f7 100644
--- a/lib/chef/resource/directory.rb
+++ b/lib/chef/resource/directory.rb
@@ -2,7 +2,7 @@
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Seth Chisamore (<schisamo@chef.io>)
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,15 +18,22 @@
# limitations under the License.
#
-require "chef/resource"
-require "chef/provider/directory"
-require "chef/mixin/securable"
+require_relative "../resource"
+require_relative "../mixin/securable"
class Chef
class Resource
class Directory < Chef::Resource
+ unified_mode true
- identity_attr :path
+ provides :directory
+
+ description "Use the **directory** resource to manage a directory, which is a hierarchy"\
+ " of folders that comprises all of the information stored on a computer."\
+ " The root directory is the top-level, under which the rest of the directory"\
+ " is organized. The directory resource uses the name property to specify the"\
+ " path to a location in a directory. Typically, permission to access that"\
+ " location in the directory is required."
state_attrs :group, :mode, :owner
@@ -35,28 +42,12 @@ class Chef
default_action :create
allowed_actions :create, :delete
- def initialize(name, run_context = nil)
- super
- @path = name
- @recursive = false
- end
-
- def recursive(arg = nil)
- set_or_return(
- :recursive,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
-
- def path(arg = nil)
- set_or_return(
- :path,
- arg,
- :kind_of => String
- )
- end
+ property :path, String, name_property: true,
+ description: "The path to the directory. Using a fully qualified path is recommended, but is not always required."
+ property :recursive, [ TrueClass, FalseClass ],
+ description: "Create or delete parent directories recursively. For the owner, group, and mode properties, the value of this property applies only to the leaf directory.",
+ default: false
end
end
end
diff --git a/lib/chef/resource/dmg_package.rb b/lib/chef/resource/dmg_package.rb
new file mode 100644
index 0000000000..c6cd04156c
--- /dev/null
+++ b/lib/chef/resource/dmg_package.rb
@@ -0,0 +1,202 @@
+#
+# Author:: Joshua Timberman (<jtimberman@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class DmgPackage < Chef::Resource
+ unified_mode true
+
+ provides(:dmg_package) { true }
+
+ description "Use the **dmg_package** resource to install a package from a .dmg file. The resource will retrieve the dmg file from a remote URL, mount it using macOS' `hdidutil`, copy the application (.app directory) to the specified destination (`/Applications`), and detach the image using `hdiutil`. The dmg file will be stored in the `Chef::Config[:file_cache_path]`."
+ introduced "14.0"
+ examples <<~DOC
+ **Install Google Chrome via the DMG package**:
+
+ ```ruby
+ dmg_package 'Google Chrome' do
+ dmg_name 'googlechrome'
+ source 'https://dl-ssl.google.com/chrome/mac/stable/GGRM/googlechrome.dmg'
+ checksum '7daa2dc5c46d9bfb14f1d7ff4b33884325e5e63e694810adc58f14795165c91a'
+ action :install
+ end
+ ```
+
+ **Install VirtualBox from the .mpkg**:
+
+ ```ruby
+ dmg_package 'Virtualbox' do
+ source 'http://dlc.sun.com.edgesuite.net/virtualbox/4.0.8/VirtualBox-4.0.8-71778-OSX.dmg'
+ type 'mpkg'
+ end
+ ```
+
+ **Install pgAdmin and automatically accept the EULA**:
+
+ ```ruby
+ dmg_package 'pgAdmin3' do
+ source 'http://wwwmaster.postgresql.org/redir/198/h/pgadmin3/release/v1.12.3/osx/pgadmin3-1.12.3.dmg'
+ checksum '9435f79d5b52d0febeddfad392adf82db9df159196f496c1ab139a6957242ce9'
+ accept_eula true
+ end
+ ```
+ DOC
+
+ property :app, String,
+ description: "The name of the application as it appears in the `/Volumes` directory if it differs from the resource block's name.",
+ name_property: true
+
+ property :source, String,
+ description: "The remote URL that is used to download the `.dmg` file, if specified."
+
+ property :file, String,
+ description: "The absolute path to the `.dmg` file on the local system."
+
+ property :owner, [String, Integer],
+ description: "The user that should own the package installation."
+
+ property :destination, String,
+ description: "The directory to copy the `.app` into.",
+ default: "/Applications"
+
+ property :checksum, String,
+ description: "The sha256 checksum of the `.dmg` file to download."
+
+ property :volumes_dir, String,
+ description: "The directory under `/Volumes` where the `dmg` is mounted if it differs from the name of the `.dmg` file.",
+ default: lazy { app }, default_description: "The value passed for the application name."
+
+ property :dmg_name, String,
+ description: "The name of the `.dmg` file if it differs from that of the app, or if the name has spaces.",
+ desired_state: false,
+ default: lazy { app }, default_description: "The value passed for the application name."
+
+ property :type, String,
+ description: "The type of package.",
+ equal_to: %w{app pkg mpkg},
+ default: "app", desired_state: false
+
+ property :package_id, String,
+ description: "The package ID that is registered with `pkgutil` when a `pkg` or `mpkg` is installed."
+
+ property :dmg_passphrase, String,
+ description: "Specify a passphrase to be used to decrypt the `.dmg` file during the mount process.",
+ desired_state: false
+
+ property :accept_eula, [TrueClass, FalseClass],
+ description: "Specify whether to accept the EULA. Certain dmg files require acceptance of EULA before mounting.",
+ default: false, desired_state: false
+
+ property :headers, Hash,
+ description: "Allows custom HTTP headers (like cookies) to be set on the `remote_file` resource.",
+ desired_state: false
+
+ property :allow_untrusted, [TrueClass, FalseClass],
+ description: "Allow installation of packages that do not have trusted certificates.",
+ default: false, desired_state: false
+
+ load_current_value do |new_resource|
+ if ::File.directory?("#{new_resource.destination}/#{new_resource.app}.app")
+ Chef::Log.info "#{new_resource.app} is already installed. To upgrade, remove \"#{new_resource.destination}/#{new_resource.app}.app\""
+ elsif shell_out("pkgutil --pkg-info '#{new_resource.package_id}'").exitstatus == 0
+ Chef::Log.info "#{new_resource.app} is already installed. To upgrade, try \"sudo pkgutil --forget '#{new_resource.package_id}'\""
+ else
+ current_value_does_not_exist! # allows us to check for current_resource.nil? below
+ end
+ end
+
+ action :install do
+ description "Installs the application."
+
+ if current_resource.nil?
+ if new_resource.source
+ remote_file dmg_file do
+ source new_resource.source
+ headers new_resource.headers if new_resource.headers
+ checksum new_resource.checksum if new_resource.checksum
+ end
+ end
+
+ unless dmg_attached?
+ converge_by "attach #{dmg_file}" do
+ raise "This DMG package requires EULA acceptance. Add 'accept_eula true' to dmg_package resource to accept the EULA during installation." if software_license_agreement? && !new_resource.accept_eula
+
+ attach_cmd = new_resource.accept_eula ? "yes | " : ""
+ attach_cmd << "/usr/bin/hdiutil attach #{passphrase_cmd} '#{dmg_file}' -nobrowse -mountpoint '/Volumes/#{new_resource.volumes_dir}'"
+
+ shell_out!(attach_cmd, env: { "PAGER" => "true" })
+ end
+ end
+
+ case new_resource.type
+ when "app"
+ execute "rsync --force --recursive --links --perms --executability --owner --group --times '/Volumes/#{new_resource.volumes_dir}/#{new_resource.app}.app' '#{new_resource.destination}'" do
+ user new_resource.owner if new_resource.owner
+ end
+
+ file "#{new_resource.destination}/#{new_resource.app}.app/Contents/MacOS/#{new_resource.app}" do
+ mode "0755"
+ ignore_failure true
+ end
+ when "mpkg", "pkg"
+ install_cmd = "installation_file=$(ls '/Volumes/#{new_resource.volumes_dir}' | grep '.#{new_resource.type}$') && sudo installer -pkg \"/Volumes/#{new_resource.volumes_dir}/$installation_file\" -target /"
+ install_cmd += " -allowUntrusted" if new_resource.allow_untrusted
+
+ execute install_cmd do
+ # Prevent cfprefsd from holding up hdiutil detach for certain disk images
+ environment("__CFPREFERENCES_AVOID_DAEMON" => "1")
+ end
+ end
+
+ execute "/usr/bin/hdiutil detach '/Volumes/#{new_resource.volumes_dir}' || /usr/bin/hdiutil detach '/Volumes/#{new_resource.volumes_dir}' -force"
+ end
+ end
+
+ action_class do
+ # @return [String] the path to the dmg file
+ def dmg_file
+ @dmg_file ||= begin
+ if new_resource.file.nil?
+ "#{Chef::Config[:file_cache_path]}/#{new_resource.dmg_name}.dmg"
+ else
+ new_resource.file
+ end
+ end
+ end
+
+ # @return [String] the hdiutil flag for handling DMGs with a password
+ def passphrase_cmd
+ @passphrase_cmd ||= new_resource.dmg_passphrase ? "-passphrase #{new_resource.dmg_passphrase}" : ""
+ end
+
+ # @return [Boolean] does the DMG require a software license agreement
+ def software_license_agreement?
+ # example hdiutil imageinfo output: http://rubular.com/r/0xvOaA6d8B
+ /Software License Agreement: true/.match?(shell_out!("/usr/bin/hdiutil imageinfo #{passphrase_cmd} '#{dmg_file}'").stdout)
+ end
+
+ # @return [Boolean] is the dmg file currently attached?
+ def dmg_attached?
+ # example hdiutil imageinfo output: http://rubular.com/r/CDcqenkixg
+ /image-path.*#{dmg_file}/.match?(shell_out!("/usr/bin/hdiutil info #{passphrase_cmd}").stdout)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/dnf_package.rb b/lib/chef/resource/dnf_package.rb
new file mode 100644
index 0000000000..80727de7d0
--- /dev/null
+++ b/lib/chef/resource/dnf_package.rb
@@ -0,0 +1,79 @@
+#
+# Copyright:: Copyright (c) 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_relative "package"
+require_relative "../mixin/which"
+require_relative "../mixin/shell_out"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Resource
+ class DnfPackage < Chef::Resource::Package
+ extend Chef::Mixin::Which
+ extend Chef::Mixin::ShellOut
+
+ unified_mode true
+ provides :dnf_package
+
+ # all rhel variants >= 8 will use DNF
+ provides :package, platform_family: "rhel", platform_version: ">= 8"
+
+ # fedora >= 22 uses DNF
+ provides :package, platform: "fedora", platform_version: ">= 22"
+
+ # amazon will eventually use DNF
+ provides :package, platform: "amazon" do
+ which("dnf")
+ end
+
+ description "Use the **dnf_package** resource to install, upgrade, and remove packages with DNF for Fedora and RHEL 8+. The dnf_package resource is able to resolve provides data for packages much like DNF can do when it is run from the command line. This allows a variety of options for installing packages, like minimum versions, virtual provides and library names."
+ introduced "12.18"
+
+ allowed_actions :install, :upgrade, :remove, :purge, :reconfig, :lock, :unlock, :flush_cache
+
+ # Install a specific arch
+ property :arch, [String, Array],
+ description: "The architecture of the package to be installed or upgraded. This value can also be passed as part of the package name.",
+ coerce: proc { |x| [x].flatten }
+
+ # Flush the in-memory available/installed cache, this does not flush the dnf caches on disk
+ property :flush_cache, Hash,
+ description: "Flush the in-memory cache before or after a DNF operation that installs, upgrades, or removes a package. DNF automatically synchronizes remote metadata to a local cache. The #{ChefUtils::Dist::Infra::CLIENT} creates a copy of the local cache, and then stores it in-memory during the #{ChefUtils::Dist::Infra::CLIENT} run. The in-memory cache allows packages to be installed during the #{ChefUtils::Dist::Infra::CLIENT} run without the need to continue synchronizing the remote metadata to the local cache while the #{ChefUtils::Dist::Infra::CLIENT} run is in-progress.",
+ default: { before: false, after: false },
+ coerce: proc { |v|
+ if v.is_a?(Hash)
+ v
+ elsif v.is_a?(Array)
+ v.each_with_object({}) { |arg, obj| obj[arg] = true }
+ elsif v.is_a?(TrueClass) || v.is_a?(FalseClass)
+ { before: v, after: v }
+ elsif v == :before
+ { before: true, after: false }
+ elsif v == :after
+ { after: true, before: false }
+ end
+ }
+
+ def allow_downgrade(arg = nil)
+ unless arg.nil?
+ Chef.deprecated(:dnf_package_allow_downgrade, "the allow_downgrade property on the dnf_package provider is not used, DNF supports downgrades by default.")
+ end
+ true
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/dpkg_package.rb b/lib/chef/resource/dpkg_package.rb
index 9ff3239884..466b17d702 100644
--- a/lib/chef/resource/dpkg_package.rb
+++ b/lib/chef/resource/dpkg_package.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,15 +16,27 @@
# limitations under the License.
#
-require "chef/resource/package"
+require_relative "package"
class Chef
class Resource
class DpkgPackage < Chef::Resource::Package
- resource_name :dpkg_package
- provides :dpkg_package, os: "linux"
+ unified_mode true
- property :source, [ String, Array, nil ]
+ provides :dpkg_package
+
+ description "Use the **dpkg_package** resource to manage packages for the dpkg platform. When a package is installed from a local file, it must be added to the node using the **remote_file** or **cookbook_file** resources."
+
+ property :source, [ String, Array, nil ],
+ description: "The path to a package in the local file system."
+
+ property :response_file, String,
+ description: "The direct path to the file used to pre-seed a package.",
+ desired_state: false
+
+ property :response_file_variables, Hash,
+ description: "A Hash of response file variables in the form of {'VARIABLE' => 'VALUE'}.",
+ default: lazy { {} }, desired_state: false
end
end
end
diff --git a/lib/chef/resource/dsc_resource.rb b/lib/chef/resource/dsc_resource.rb
index 58594cce7b..679deef47b 100644
--- a/lib/chef/resource/dsc_resource.rb
+++ b/lib/chef/resource/dsc_resource.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Edwards (<adamed@chef.io>)
#
-# Copyright:: Copyright 2014-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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.
@@ -15,12 +15,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-require "chef/dsl/powershell"
+require_relative "../dsl/powershell"
class Chef
class Resource
class DscResource < Chef::Resource
- provides :dsc_resource, os: "windows"
+ unified_mode true
+
+ provides :dsc_resource
+
+ description "The dsc_resource resource allows any DSC resource to be used in a recipe, as well as any custom resources that have been added to your Windows PowerShell environment. Microsoft frequently adds new resources to the DSC resource collection."
+ introduced "12.2"
# This class will check if the object responds to
# to_text. If it does, it will call that as opposed
@@ -29,7 +34,7 @@ class Chef
# to dump the actual ivars
class ToTextHash < Hash
def to_text
- descriptions = self.map do |(property, obj)|
+ descriptions = map do |(property, obj)|
obj_text = if obj.respond_to?(:to_text)
obj.to_text
else
@@ -37,7 +42,7 @@ class Chef
end
"#{property}=>#{obj_text}"
end
- "{#{descriptions.join(', ')}}"
+ "{#{descriptions.join(", ")}}"
end
end
@@ -49,7 +54,6 @@ class Chef
super
@properties = ToTextHash.new
@resource = nil
- @reboot_action = :nothing
end
def resource(value = nil)
@@ -68,8 +72,12 @@ class Chef
end
end
+ property :module_version, String,
+ introduced: "12.21",
+ description: "The version number of the module to use. PowerShell 5.0.10018.0 (or higher) supports having multiple versions of a module installed. This should be specified along with the module_name."
+
def property(property_name, value = nil)
- if not property_name.is_a?(Symbol)
+ unless 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
@@ -91,21 +99,14 @@ class Chef
# 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
+ property :reboot_action, Symbol, default: :nothing, equal_to: %i{nothing reboot_now request_reboot},
+ introduced: "12.6",
+ description: "Use to request an immediate reboot or to queue a reboot using the :reboot_now (immediate reboot) or :request_reboot (queued reboot) actions built into the reboot resource."
- def timeout(arg = nil)
- set_or_return(
- :timeout,
- arg,
- :kind_of => [ Integer ]
- )
- end
+ property :timeout, Integer,
+ introduced: "12.6",
+ description: "The amount of time (in seconds) a command is to wait before timing out.",
+ desired_state: false
private
diff --git a/lib/chef/resource/dsc_script.rb b/lib/chef/resource/dsc_script.rb
index 7da29a651a..3a14da15f0 100644
--- a/lib/chef/resource/dsc_script.rb
+++ b/lib/chef/resource/dsc_script.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Edwards (<adamed@chef.io>)
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,15 +16,27 @@
# limitations under the License.
#
-require "chef/exceptions"
-require "chef/dsl/powershell"
+require_relative "../resource"
+require_relative "../exceptions"
+require_relative "../dsl/powershell"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Resource
class DscScript < Chef::Resource
include Chef::DSL::Powershell
- provides :dsc_script, os: "windows"
+ unified_mode true
+ provides :dsc_script
+
+ description <<~DESC
+ Many DSC resources are comparable to built-in #{ChefUtils::Dist::Infra::PRODUCT} resources. For example, both DSC and #{ChefUtils::Dist::Infra::PRODUCT}
+ have file, package, and service resources. The dsc_script resource is most useful for those DSC resources that do not have a direct comparison to a
+ resource in #{ChefUtils::Dist::Infra::PRODUCT}, such as the Archive resource, a custom DSC resource, an existing DSC script that performs an important
+ task, and so on. Use the dsc_script resource to embed the code that defines a DSC configuration directly within a #{ChefUtils::Dist::Infra::PRODUCT} recipe.
+
+ Warning: The **dsc_script** resource may not be used with the 32 bit Chef Infra client. It must be executed from a 64 bit Chef Infra client.
+ DESC
default_action :run
@@ -35,59 +47,64 @@ class Chef
def code(arg = nil)
if arg && command
- raise ArgumentError, "Only one of 'code' and 'command' attributes may be specified"
+ raise ArgumentError, "Only one of 'code' and 'command' properties may be specified"
end
if arg && configuration_name
- raise ArgumentError, "The 'code' and 'command' attributes may not be used together"
+ raise ArgumentError, "The 'code' and 'command' properties may not be used together"
end
+
set_or_return(
:code,
arg,
- :kind_of => [ String ]
+ kind_of: [ String ]
)
end
def configuration_name(arg = nil)
if arg && code
- raise ArgumentError, "Attribute `configuration_name` may not be set if `code` is set"
+ raise ArgumentError, "Property `configuration_name` may not be set if `code` is set"
end
+
set_or_return(
:configuration_name,
arg,
- :kind_of => [ String ]
+ kind_of: [ String ]
)
end
def command(arg = nil)
if arg && code
- raise ArgumentError, "The 'code' and 'command' attributes may not be used together"
+ raise ArgumentError, "The 'code' and 'command' properties may not be used together"
end
+
set_or_return(
:command,
arg,
- :kind_of => [ String ]
+ kind_of: [ String ]
)
end
def configuration_data(arg = nil)
if arg && configuration_data_script
- raise ArgumentError, "The 'configuration_data' and 'configuration_data_script' attributes may not be used together"
+ raise ArgumentError, "The 'configuration_data' and 'configuration_data_script' properties may not be used together"
end
+
set_or_return(
:configuration_data,
arg,
- :kind_of => [ String ]
+ kind_of: [ String ]
)
end
def configuration_data_script(arg = nil)
if arg && configuration_data
- raise ArgumentError, "The 'configuration_data' and 'configuration_data_script' attributes may not be used together"
+ raise ArgumentError, "The 'configuration_data' and 'configuration_data_script' properties may not be used together"
end
+
set_or_return(
:configuration_data_script,
arg,
- :kind_of => [ String ]
+ kind_of: [ String ]
)
end
@@ -104,37 +121,18 @@ class Chef
end
end
- def flags(arg = nil)
- set_or_return(
- :flags,
- arg,
- :kind_of => [ Hash ]
- )
- end
+ property :flags, Hash,
+ description: "Pass parameters to the DSC script that is specified by the command property. Parameters are defined as key-value pairs, where the value of each key is the parameter to pass. This property may not be used in the same recipe as the code property."
- def cwd(arg = nil)
- set_or_return(
- :cwd,
- arg,
- :kind_of => [ String ]
- )
- end
+ property :cwd, String,
+ description: "The current working directory."
- def environment(arg = nil)
- set_or_return(
- :environment,
- arg,
- :kind_of => [ Hash ]
- )
- end
+ property :environment, Hash,
+ description: "A Hash of environment variables in the form of ({'ENV_VARIABLE' => 'VALUE'}). (These variables must exist for a command to be run successfully)."
- def timeout(arg = nil)
- set_or_return(
- :timeout,
- arg,
- :kind_of => [ Integer ]
- )
- end
+ property :timeout, Integer,
+ description: "The amount of time (in seconds) a command is to wait before timing out.",
+ desired_state: false
end
end
end
diff --git a/lib/chef/resource/env.rb b/lib/chef/resource/env.rb
deleted file mode 100644
index 7fac8af40b..0000000000
--- a/lib/chef/resource/env.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-#
-# Author:: Doug MacEachern (<dougm@vmware.com>)
-# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2010-2016, VMware, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-class Chef
- class Resource
- class Env < Chef::Resource
-
- identity_attr :key_name
-
- state_attrs :value
-
- provides :env, os: "windows"
-
- default_action :create
- allowed_actions :create, :delete, :modify
-
- def initialize(name, run_context = nil)
- super
- @key_name = name
- @value = nil
- @delim = nil
- end
-
- def key_name(arg = nil)
- set_or_return(
- :key_name,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def value(arg = nil)
- set_or_return(
- :value,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def delim(arg = nil)
- set_or_return(
- :delim,
- arg,
- :kind_of => [ String ]
- )
- end
- end
- end
-end
diff --git a/lib/chef/resource/erl_call.rb b/lib/chef/resource/erl_call.rb
deleted file mode 100644
index 3e317676a5..0000000000
--- a/lib/chef/resource/erl_call.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-#
-# Author:: Joe Williams (<joe@joetify.com>)
-# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2009-2016, 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"
-require "chef/provider/erl_call"
-
-class Chef
- class Resource
- class ErlCall < Chef::Resource
-
- # erl_call : http://erlang.org/doc/man/erl_call.html
-
- identity_attr :code
-
- default_action :run
-
- def initialize(name, run_context = nil)
- super
-
- @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
- end
-
- def code(arg = nil)
- set_or_return(
- :code,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def cookie(arg = nil)
- set_or_return(
- :cookie,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def distributed(arg = nil)
- set_or_return(
- :distributed,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
-
- def name_type(arg = nil)
- set_or_return(
- :name_type,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def node_name(arg = nil)
- set_or_return(
- :node_name,
- arg,
- :kind_of => [ String ]
- )
- end
-
- end
- end
-end
diff --git a/lib/chef/resource/execute.rb b/lib/chef/resource/execute.rb
index 1a56607267..5a78160642 100644
--- a/lib/chef/resource/execute.rb
+++ b/lib/chef/resource/execute.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,14 +17,486 @@
# limitations under the License.
#
-require "chef/resource"
-require "chef/provider/execute"
+require_relative "../resource"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Resource
class Execute < Chef::Resource
+ unified_mode true
- identity_attr :command
+ provides :execute, target_mode: true
+
+ description "Use the **execute** resource to execute a single command. Commands that are executed with this resource are (by their nature) not idempotent, as they are typically unique to the environment in which they are run. Use `not_if` and `only_if` to guard this resource for idempotence. Note: Use the **script** resource to execute a script using a specific interpreter (Ruby, Python, Perl, csh, or Bash)."
+
+ examples <<~EXAMPLES
+ **Run a command upon notification**:
+
+ ```ruby
+ execute 'slapadd' do
+ command 'slapadd < /tmp/something.ldif'
+ creates '/var/lib/slapd/uid.bdb'
+
+ action :nothing
+ end
+
+ template '/tmp/something.ldif' do
+ source 'something.ldif'
+
+ notifies :run, 'execute[slapadd]', :immediately
+ end
+ ```
+
+ **Run a touch file only once while running a command**:
+
+ ```ruby
+ execute 'upgrade script' do
+ command 'php upgrade-application.php && touch /var/application/.upgraded'
+
+ creates '/var/application/.upgraded'
+ action :run
+ end
+ ```
+
+ **Run a command which requires an environment variable**:
+
+ ```ruby
+ execute 'slapadd' do
+ command 'slapadd < /tmp/something.ldif'
+ creates '/var/lib/slapd/uid.bdb'
+
+ action :run
+ environment ({'HOME' => '/home/my_home'})
+ end
+ ```
+
+ **Delete a repository using yum to scrub the cache**:
+
+ ```ruby
+ # the following code sample thanks to gaffneyc @ https://gist.github.com/918711
+ execute 'clean-yum-cache' do
+ command 'yum clean all'
+ action :nothing
+ end
+
+ file '/etc/yum.repos.d/bad.repo' do
+ action :delete
+ notifies :run, 'execute[clean-yum-cache]', :immediately
+ end
+ ```
+
+ **Prevent restart and reconfigure if configuration is broken**:
+
+ Use the `:nothing` action (common to all resources) to prevent the test from
+ starting automatically, and then use the `subscribes` notification to run a
+ configuration test when a change to the template is detected.
+
+ ```ruby
+ execute 'test-nagios-config' do
+ command 'nagios3 --verify-config'
+ action :nothing
+ subscribes :run, 'template[/etc/nagios3/configures-nagios.conf]', :immediately
+ end
+ ```
+
+ **Notify in a specific order**:
+
+ To notify multiple resources, and then have these resources run in a certain
+ order, do something like the following.
+
+ ```ruby
+ execute 'foo' do
+ command '...'
+ notifies :create, 'template[baz]', :immediately
+ notifies :install, 'package[bar]', :immediately
+ notifies :run, 'execute[final]', :immediately
+ end
+
+ template 'baz' do
+ #...
+ notifies :run, 'execute[restart_baz]', :immediately
+ end
+
+ package 'bar'
+ execute 'restart_baz'
+ execute 'final' do
+ command '...'
+ end
+ ```
+
+ where the sequencing will be in the same order as the resources are listed in
+ the recipe: `execute 'foo'`, `template 'baz'`, `execute [restart_baz]`,
+ `package 'bar'`, and `execute 'final'`.
+
+ **Execute a command using a template**:
+
+ The following example shows how to set up IPv4 packet forwarding using the
+ **execute** resource to run a command named `forward_ipv4` that uses a template
+ defined by the **template** resource.
+
+ ```ruby
+ execute 'forward_ipv4' do
+ command 'echo > /proc/.../ipv4/ip_forward'
+ action :nothing
+ end
+
+ template '/etc/file_name.conf' do
+ source 'routing/file_name.conf.erb'
+
+ notifies :run, 'execute[forward_ipv4]', :delayed
+ end
+ ```
+
+ where the `command` property for the **execute** resource contains the command
+ that is to be run and the `source` property for the **template** resource
+ specifies which template to use. The `notifies` property for the **template**
+ specifies that the `execute[forward_ipv4]` (which is defined by the **execute**
+ resource) should be queued up and run at the end of a Chef Infra Client run.
+
+ **Add a rule to an IP table**:
+
+ The following example shows how to add a rule named `test_rule` to an IP table
+ using the **execute** resource to run a command using a template that is defined
+ by the **template** resource:
+
+ ```ruby
+ execute 'test_rule' do
+ command "command_to_run
+ --option value
+ --option value
+ --source \#{node[:name_of_node][:ipsec][:local][:subnet]}
+ -j test_rule"
+
+ action :nothing
+ end
+
+ template '/etc/file_name.local' do
+ source 'routing/file_name.local.erb'
+ notifies :run, 'execute[test_rule]', :delayed
+ end
+ ```
+
+ where the `command` property for the **execute** resource contains the command
+ that is to be run and the `source` property for the **template** resource
+ specifies which template to use. The `notifies` property for the **template**
+ specifies that the `execute[test_rule]` (which is defined by the **execute**
+ resource) should be queued up and run at the end of a Chef Infra Client run.
+
+ **Stop a service, do stuff, and then restart it**:
+
+ The following example shows how to use the **execute**, **service**, and
+ **mount** resources together to ensure that a node running on Amazon EC2 is
+ running MySQL. This example does the following:
+
+ - Checks to see if the Amazon EC2 node has MySQL
+ - If the node has MySQL, stops MySQL
+ - Installs MySQL
+ - Mounts the node
+ - Restarts MySQL
+
+ ```ruby
+ # the following code sample comes from the ``server_ec2``
+ # recipe in the following cookbook:
+ # https://github.com/chef-cookbooks/mysql
+
+ if (node.attribute?('ec2') && !FileTest.directory?(node['mysql']['ec2_path']))
+ service 'mysql' do
+ action :stop
+ end
+
+ execute 'install-mysql' do
+ command "mv \#{node['mysql']['data_dir']} \#{node['mysql']['ec2_path']}"
+ not_if { ::File.directory?(node['mysql']['ec2_path']) }
+ end
+
+ [node['mysql']['ec2_path'], node['mysql']['data_dir']].each do |dir|
+ directory dir do
+ owner 'mysql'
+ group 'mysql'
+ end
+ end
+
+ mount node['mysql']['data_dir'] do
+ device node['mysql']['ec2_path']
+ fstype 'none'
+ options 'bind,rw'
+ action [:mount, :enable]
+ end
+
+ service 'mysql' do
+ action :start
+ end
+ end
+ ```
+
+ where
+
+ - the two **service** resources are used to stop, and then restart the MySQL service
+ - the **execute** resource is used to install MySQL
+ - the **mount** resource is used to mount the node and enable MySQL
+
+ **Use the platform_family? method**:
+
+ The following is an example of using the `platform_family?` method in the Recipe
+ DSL to create a variable that can be used with other resources in the same
+ recipe. In this example, `platform_family?` is being used to ensure that a
+ specific binary is used for a specific platform before using the **remote_file**
+ resource to download a file from a remote location, and then using the
+ **execute** resource to install that file by running a command.
+
+ ```ruby
+ if platform_family?('rhel')
+ pip_binary = '/usr/bin/pip'
+ else
+ pip_binary = '/usr/local/bin/pip'
+ end
+
+ remote_file "\#{Chef::Config[:file_cache_path]}/distribute_setup.py" do
+ source 'http://python-distribute.org/distribute_setup.py'
+ mode '0755'
+ not_if { ::File.exist?(pip_binary) }
+ end
+
+ execute 'install-pip' do
+ cwd Chef::Config[:file_cache_path]
+ command <<~EOF
+ # command for installing Python goes here
+ EOF
+ not_if { ::File.exist?(pip_binary) }
+ end
+ ```
+
+ where a command for installing Python might look something like:
+
+ ```ruby
+ \#{node['python']['binary']} distribute_setup.py \#{::File.dirname(pip_binary)}/easy_install pip
+ ```
+
+ **Control a service using the execute resource**:
+
+ <div class="admonition-warning">
+ <p class="admonition-warning-title">Warning</p>
+ <div class="admonition-warning-text">
+ This is an example of something that should NOT be done. Use the **service**
+ resource to control a service, not the **execute** resource.
+ </div>
+ </div>
+
+ Do something like this:
+
+ ```ruby
+ service 'tomcat' do
+ action :start
+ end
+ ```
+
+ and NOT something like this:
+
+ ```ruby
+ execute 'start-tomcat' do
+ command '/etc/init.d/tomcat start'
+ action :run
+ end
+ ```
+
+ There is no reason to use the **execute** resource to control a service because
+ the **service** resource exposes the `start_command` property directly, which
+ gives a recipe full control over the command issued in a much cleaner, more
+ direct manner.
+
+ **Use the search recipe DSL method to find users**:
+
+ The following example shows how to use the `search` method in the Recipe DSL to
+ search for users:
+
+ ```ruby
+ # the following code sample comes from the openvpn cookbook:
+
+ search("users", "*:*") do |u|
+ execute "generate-openvpn-\#{u['id']}" do
+ command "./pkitool \#{u['id']}"
+ cwd '/etc/openvpn/easy-rsa'
+ end
+
+ %w{ conf ovpn }.each do |ext|
+ template "\#{node['openvpn']['key_dir']}/\#{u['id']}.\#{ext}" do
+ source 'client.conf.erb'
+ variables :username => u['id']
+ end
+ end
+ end
+ ```
+
+ where
+
+ - the search data will be used to create **execute** resources
+ - the **template** resource tells Chef Infra Client which template to use
+
+ **Enable remote login for macOS**:
+
+ ```ruby
+ execute 'enable ssh' do
+ command '/usr/sbin/systemsetup -setremotelogin on'
+ not_if '/usr/sbin/systemsetup -getremotelogin | /usr/bin/grep On'
+ action :run
+ end
+ ```
+
+ **Execute code immediately, based on the template resource**:
+
+ By default, notifications are `:delayed`, that is they are queued up as they are
+ triggered, and then executed at the very end of a Chef Infra Client run. To run
+ kan action immediately, use `:immediately`:
+
+ ```ruby
+ template '/etc/nagios3/configures-nagios.conf' do
+ # other parameters
+ notifies :run, 'execute[test-nagios-config]', :immediately
+ end
+ ```
+
+ and then Chef Infra Client would immediately run the following:
+
+ ```ruby
+ execute 'test-nagios-config' do
+ command 'nagios3 --verify-config'
+ action :nothing
+ end
+ ```
+
+ **Sourcing a file**:
+
+ The **execute** resource cannot be used to source a file (e.g. `command 'source
+ filename'`). The following example will fail because `source` is not an
+ executable:
+
+ ```ruby
+ execute 'foo' do
+ command 'source /tmp/foo.sh'
+ end
+ ```
+
+
+ Instead, use the **script** resource or one of the **script**-based resources
+ (**bash**, **csh**, **perl**, **python**, or **ruby**). For example:
+
+ ```ruby
+ bash 'foo' do
+ code 'source /tmp/foo.sh'
+ end
+ ```
+
+ **Run a Knife command**:
+
+ ```ruby
+ execute 'create_user' do
+ command <<~EOM
+ knife user create \#{user}
+ --admin
+ --password password
+ --disable-editing
+ --file /home/vagrant/.chef/user.pem
+ --config /tmp/knife-admin.rb
+ EOM
+ end
+ ```
+
+ **Run install command into virtual environment**:
+
+ The following example shows how to install a lightweight JavaScript framework
+ into Vagrant:
+
+ ```ruby
+ execute "install q and zombiejs" do
+ cwd "/home/vagrant"
+ user "vagrant"
+ environment ({'HOME' => '/home/vagrant', 'USER' => 'vagrant'})
+ command "npm install -g q zombie should mocha coffee-script"
+ action :run
+ end
+ ```
+
+ **Run a command as a named user**:
+
+ The following example shows how to run `bundle install` from a Chef Infra Client
+ run as a specific user. This will put the gem into the path of the user
+ (`vagrant`) instead of the root user (under which the Chef Infra Client runs):
+
+ ```ruby
+ execute '/opt/chefdk/embedded/bin/bundle install' do
+ cwd node['chef_workstation']['bundler_path']
+ user node['chef_workstation']['user']
+
+ environment ({
+ 'HOME' => "/home/\#{node['chef_workstation']['user']}",
+ 'USER' => node['chef_workstation']['user']
+ })
+ not_if 'bundle check'
+ end
+ ```
+
+ **Run a command as an alternate user**:
+
+ *Note*: When Chef is running as a service, this feature requires that the user
+ that Chef runs as has 'SeAssignPrimaryTokenPrivilege' (aka
+ 'SE_ASSIGNPRIMARYTOKEN_NAME') user right. By default only LocalSystem and
+ NetworkService have this right when running as a service. This is necessary
+ even if the user is an Administrator.
+
+ This right can be added and checked in a recipe using this example:
+
+ ```ruby
+ # Add 'SeAssignPrimaryTokenPrivilege' for the user
+ Chef::ReservedNames::Win32::Security.add_account_right('<user>', 'SeAssignPrimaryTokenPrivilege')
+
+ # Check if the user has 'SeAssignPrimaryTokenPrivilege' rights
+ Chef::ReservedNames::Win32::Security.get_account_right('<user>').include?('SeAssignPrimaryTokenPrivilege')
+ ```
+
+ The following example shows how to run `mkdir test_dir` from a Chef Infra Client
+ run as an alternate user.
+
+ ```ruby
+ # Passing only username and password
+ execute 'mkdir test_dir' do
+ cwd Chef::Config[:file_cache_path]
+
+ user "username"
+ password "password"
+ end
+
+ # Passing username and domain
+ execute 'mkdir test_dir' do
+ cwd Chef::Config[:file_cache_path]
+
+ domain "domain-name"
+ user "user"
+ password "password"
+ end
+
+ # Passing username = 'domain-name\\username'. No domain is passed
+ execute 'mkdir test_dir' do
+ cwd Chef::Config[:file_cache_path]
+
+ user "domain-name\\username"
+ password "password"
+ end
+
+ # Passing username = 'username@domain-name'. No domain is passed
+ execute 'mkdir test_dir' do
+ cwd Chef::Config[:file_cache_path]
+
+ user "username@domain-name"
+ password "password"
+ end
+ ```
+
+ **Run a command with an external input file**:
+
+ execute 'md5sum' do
+ input File.read(__FILE__)
+ end
+ EXAMPLES
# The ResourceGuardInterpreter wraps a resource's guards in another resource. That inner resource
# needs to behave differently during (for example) why_run mode, so we flag it here. For why_run mode
@@ -37,111 +509,70 @@ class Chef
def initialize(name, run_context = nil)
super
@command = name
- @backup = 5
- @creates = nil
- @cwd = nil
- @environment = nil
- @group = nil
- @path = nil
- @returns = 0
- @timeout = nil
- @user = nil
- @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 ]
- )
- end
+ property :command, [ String, Array ],
+ name_property: true,
+ description: "An optional property to set the command to be executed if it differs from the resource block's name."
- def command(arg = nil)
- set_or_return(
- :command,
- arg,
- :kind_of => [ String, Array ]
- )
- end
+ property :umask, [ String, Integer ],
+ description: "The file mode creation mask, or umask."
- def creates(arg = nil)
- set_or_return(
- :creates,
- arg,
- :kind_of => [ String ]
- )
- end
+ property :creates, String,
+ description: "Prevent a command from creating a file when that file already exists."
- def cwd(arg = nil)
- set_or_return(
- :cwd,
- arg,
- :kind_of => [ String ]
- )
- end
+ property :cwd, String,
+ description: "The current working directory from which the command will be run."
- def environment(arg = nil)
- set_or_return(
- :environment,
- arg,
- :kind_of => [ Hash ]
- )
- end
+ property :environment, Hash,
+ description: "A Hash of environment variables in the form of `({'ENV_VARIABLE' => 'VALUE'})`. **Note**: These variables must exist for a command to be run successfully."
- alias :env :environment
+ property :group, [ String, Integer ],
+ description: "The group name or group ID that must be changed before running a command."
- def group(arg = nil)
- set_or_return(
- :group,
- arg,
- :kind_of => [ String, Integer ]
- )
- end
+ property :live_stream, [ TrueClass, FalseClass ], default: false,
+ description: "Send the output of the command run by this execute resource block to the #{ChefUtils::Dist::Infra::PRODUCT} event stream."
- def live_stream(arg = nil)
- set_or_return(
- :live_stream,
- arg,
- :kind_of => [ TrueClass, FalseClass ])
- end
+ # default_env defaults to `false` so that the command execution more exactly matches what the user gets on the command line without magic
+ property :default_env, [ TrueClass, FalseClass ], desired_state: false, default: false,
+ introduced: "14.2",
+ description: "When true this enables ENV magic to add path_sanity to the PATH and force the locale to English+UTF-8 for parsing output"
- def path(arg = nil)
- 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."
+ property :returns, [ Integer, Array ], default: 0,
+ description: "The return value for a command. This may be an array of accepted values. An exception is raised when the return value(s) do not match."
- set_or_return(
- :path,
- arg,
- :kind_of => [ Array ]
- )
- end
+ property :timeout, [ Integer, String, Float ],
+ default: 3600,
+ description: "The amount of time (in seconds) a command is to wait before timing out.",
+ desired_state: false
- def returns(arg = nil)
- set_or_return(
- :returns,
- arg,
- :kind_of => [ Integer, Array ]
- )
- end
+ property :user, [ String, Integer ],
+ description: "The user name of the user identity with which to launch the new process. The user name may optionally be specified with a domain, i.e. `domainuser` or `user@my.dns.domain.com` via Universal Principal Name (UPN)format. It can also be specified without a domain simply as user if the domain is instead specified using the domain property. On Windows only, if this property is specified, the password property must be specified."
- def timeout(arg = nil)
- set_or_return(
- :timeout,
- arg,
- :kind_of => [ Integer, Float ]
- )
- end
+ property :domain, String,
+ introduced: "12.21",
+ description: "Windows only: The domain of the user user specified by the user property. If not specified, the username and password specified by the `user` and `password` properties will be used to resolve that user against the domain in which the system running #{ChefUtils::Dist::Infra::PRODUCT} is joined, or if that system is not joined to a domain it will resolve the user as a local account on that system. An alternative way to specify the domain is to leave this property unspecified and specify the domain as part of the user property."
- def user(arg = nil)
- set_or_return(
- :user,
- arg,
- :kind_of => [ String, Integer ]
- )
- end
+ property :password, String, sensitive: true,
+ introduced: "12.21",
+ description: "Windows only: The password of the user specified by the user property. This property is mandatory if user is specified on Windows and may only be specified if user is specified. The sensitive property for this resource will automatically be set to true if password is specified."
+
+ # lazy used to set default value of sensitive to true if password is set
+ property :sensitive, [ TrueClass, FalseClass ],
+ description: "Ensure that sensitive resource data is not logged by the #{ChefUtils::Dist::Infra::PRODUCT}.",
+ default: lazy { password ? true : false }, default_description: "True if the password property is set. False otherwise."
+
+ property :elevated, [ TrueClass, FalseClass ], default: false,
+ description: "Determines whether the script will run with elevated permissions to circumvent User Access Control (UAC) from interactively blocking the process.\nThis will cause the process to be run under a batch login instead of an interactive login. The user running #{ChefUtils::Dist::Infra::CLIENT} needs the 'Replace a process level token' and 'Adjust Memory Quotas for a process' permissions. The user that is running the command needs the 'Log on as a batch job' permission.\nBecause this requires a login, the user and password properties are required.",
+ introduced: "13.3"
+
+ property :input, [String],
+ introduced: "16.2",
+ description: "An optional property to set the input sent to the command as STDIN."
+
+ alias :env :environment
def self.set_guard_inherited_attributes(*inherited_attributes)
@class_inherited_attributes = inherited_attributes
@@ -156,13 +587,84 @@ class Chef
ancestor_attributes = superclass.guard_inherited_attributes
end
- ancestor_attributes.concat(@class_inherited_attributes ? @class_inherited_attributes : []).uniq
+ ancestor_attributes.concat(@class_inherited_attributes || []).uniq
+ end
+
+ # post resource creation validation
+ #
+ # @return [void]
+ def after_created
+ validate_identity_platform(user, password, domain, elevated)
+ identity = qualify_user(user, password, domain)
+ domain(identity[:domain])
+ user(identity[:user])
+ end
+
+ def validate_identity_platform(specified_user, password = nil, specified_domain = nil, elevated = false)
+ if windows?
+ if specified_user && password.nil?
+ raise ArgumentError, "A value for `password` must be specified when a value for `user` is specified on the Windows platform"
+ end
+
+ if elevated && !specified_user && !password
+ raise ArgumentError, "`elevated` option should be passed only with `username` and `password`."
+ end
+ else
+ if password || specified_domain
+ raise Exceptions::UnsupportedPlatform, "Values for `domain` and `password` are only supported on the Windows platform"
+ end
+
+ if elevated
+ raise Exceptions::UnsupportedPlatform, "Value for `elevated` is only supported on the Windows platform"
+ end
+ end
+ end
+
+ def qualify_user(specified_user, password = nil, specified_domain = nil)
+ domain = specified_domain
+ user = specified_user
+
+ if specified_user.nil? && ! specified_domain.nil?
+ raise ArgumentError, "The domain `#{specified_domain}` was specified, but no user name was given"
+ end
+
+ # if domain is provided in both username and domain
+ if specified_user.is_a?(String) && ((specified_user.include? '\\') || (specified_user.include? "@")) && specified_domain
+ raise ArgumentError, "The domain is provided twice. Username: `#{specified_user}`, Domain: `#{specified_domain}`. Please specify domain only once."
+ end
+
+ if specified_user.is_a?(String) && specified_domain.nil?
+ # Splitting username of format: Domain\Username
+ domain_and_user = user.split('\\')
+
+ if domain_and_user.length == 2
+ domain = domain_and_user[0]
+ user = domain_and_user[1]
+ elsif domain_and_user.length == 1
+ # Splitting username of format: Username@Domain
+ domain_and_user = user.split("@")
+ if domain_and_user.length == 2
+ domain = domain_and_user[1]
+ user = domain_and_user[0]
+ elsif domain_and_user.length != 1
+ raise ArgumentError, "The specified user name `#{user}` is not a syntactically valid user name"
+ end
+ end
+ end
+
+ if ( password || domain ) && user.nil?
+ raise ArgumentError, "A value for `password` or `domain` was specified without specification of a value for `user`"
+ end
+
+ { domain: domain, user: user }
end
set_guard_inherited_attributes(
:cwd,
+ :domain,
:environment,
:group,
+ :password,
:user,
:umask
)
diff --git a/lib/chef/resource/file.rb b/lib/chef/resource/file.rb
index 207de63778..b2bba06185 100644
--- a/lib/chef/resource/file.rb
+++ b/lib/chef/resource/file.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Seth Chisamore (<schisamo@chef.io>)
-# Copyright:: Copyright 2008-2016, 2011-2015 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,18 +17,24 @@
# limitations under the License.
#
-require "chef/resource"
-require "chef/platform/query_helpers"
-require "chef/mixin/securable"
-require "chef/resource/file/verification"
-require "pathname"
+require_relative "../resource"
+require_relative "../platform/query_helpers"
+require_relative "../mixin/securable"
+require_relative "file/verification"
+require "pathname" unless defined?(Pathname)
+require "chef-utils" unless defined?(ChefUtils::CANARY)
class Chef
class Resource
class File < Chef::Resource
include Chef::Mixin::Securable
+ unified_mode true
- if Platform.windows?
+ provides :file
+
+ description "Use the **file** resource to manage files directly on a node. Note: Use the **cookbook_file** resource to copy a file from a cookbook's `/files` directory. Use the **template** resource to create a file based on a template in a cookbook's `/templates` directory. And use the **remote_file** resource to transfer a file to a node from a remote location."
+
+ if ChefUtils.windows?
# Use Windows rights instead of standard *nix permissions
state_attrs :checksum, :rights, :deny_rights
else
@@ -43,24 +49,42 @@ class Chef
# 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
+ # @return [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: lazy { |r| r.docker? && r.special_docker_files?(r.path) ? false : 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 :path, String, name_property: true,
+ description: "The full path to the file, including the file name and its extension. For example: /files/file.txt. Default value: the name of the resource block. Microsoft Windows: A path that begins with a forward slash `/` will point to the root of the current working directory of the #{ChefUtils::Dist::Infra::PRODUCT} process. This path can vary from system to system. Therefore, using a path that begins with a forward slash `/` is not recommended."
+
+ property :atomic_update, [ TrueClass, FalseClass ], desired_state: false, default: lazy { docker? && special_docker_files?(path) ? false : Chef::Config[:file_atomic_update] },
+ default_description: "False if modifying /etc/hosts, /etc/hostname, or /etc/resolv.conf within Docker containers. Otherwise default to the client.rb 'file_atomic_update' config value.",
+ description: "Perform atomic file updates on a per-resource basis. Set to true for atomic file updates. Set to false for non-atomic file updates. This setting overrides `file_atomic_update`, which is a global setting found in the `client.rb` file."
+
+ property :backup, [ Integer, FalseClass ], desired_state: false, default: 5,
+ description: "The number of backups to be kept in `/var/chef/backup` (for UNIX- and Linux-based platforms) or `C:/chef/backup` (for the Microsoft Windows platform). Set to `false` to prevent backups from being kept."
+
+ property :checksum, [ String, nil ],
+ regex: /^\h{64}$/,
+ coerce: lambda { |s| s.is_a?(String) ? s.downcase : s },
+ description: "The SHA-256 checksum of the file. Use to ensure that a specific file is used. If the checksum does not match, the file is not used."
+
+ property :content, [ String, nil ], desired_state: false,
+ description: "A string that is written to the file. The contents of this property replace any previous content when this property has something other than the default value. The default behavior will not modify content."
+
+ property :diff, [ String, nil ], desired_state: false, skip_docs: true
+
+ property :force_unlink, [ TrueClass, FalseClass ], desired_state: false, default: false,
+ description: "How #{ChefUtils::Dist::Infra::PRODUCT} handles certain situations when the target file turns out not to be a file. For example, when a target file is actually a symlink. Set to `true` for #{ChefUtils::Dist::Infra::PRODUCT} to delete the non-file target and replace it with the specified file. Set to `false` for #{ChefUtils::Dist::Infra::PRODUCT} to raise an error."
+
+ property :manage_symlink_source, [ TrueClass, FalseClass ], desired_state: false,
+ description: "Change the behavior of the file resource if it is pointed at a symlink. When this value is set to true, #{ChefUtils::Dist::Infra::PRODUCT} will manage the symlink's permissions or will replace the symlink with a normal file if the resource has content. When this value is set to false, #{ChefUtils::Dist::Infra::PRODUCT} will follow the symlink and will manage the permissions and content of symlink's target file. The default behavior is true but emits a warning that the default value will be changed to false in a future version; setting this explicitly to true or false suppresses this warning."
+
property :verifications, Array, default: lazy { [] }
def verify(command = nil, opts = {}, &block)
- if ! (command.nil? || [String, Symbol].include?(command.class))
+ unless command.nil? || [String, Symbol].include?(command.class)
raise ArgumentError, "verify requires either a string, symbol, or a block"
end
diff --git a/lib/chef/resource/file/verification.rb b/lib/chef/resource/file/verification.rb
index e11035d33f..7c5299af5a 100644
--- a/lib/chef/resource/file/verification.rb
+++ b/lib/chef/resource/file/verification.rb
@@ -1,6 +1,6 @@
#
# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright 2014-2016, Chef Software, Inc
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/exceptions"
-require "chef/guard_interpreter"
-require "chef/mixin/descendants_tracker"
+require_relative "../../exceptions"
+require_relative "../../guard_interpreter"
+require_relative "../../mixin/descendants_tracker"
class Chef
class Resource
@@ -36,7 +36,7 @@ class Chef
# ruby block, which will be called, or a string, which will be
# executed as a Shell command.
#
- # Additonally, Chef or third-party verifications can ship
+ # Additionally, Chef or third-party verifications can ship
# "registered verifications" that the user can use by specifying
# a :symbol as the command name.
#
@@ -63,6 +63,7 @@ class Chef
class Verification
extend Chef::Mixin::DescendantsTracker
+ attr_reader :output
def self.provides(name)
@provides = name
@@ -77,9 +78,14 @@ class Chef
if c.nil?
raise Chef::Exceptions::VerificationNotFound.new "No file verification for #{name} found."
end
+
c
end
+ def logger
+ @parent_resource.logger
+ end
+
def initialize(parent_resource, command, opts, &block)
@command, @command_opts = command, opts
@block = block
@@ -87,7 +93,7 @@ class Chef
end
def verify(path, opts = {})
- Chef::Log.debug("Running verification[#{self}] on #{path}")
+ logger.trace("Running verification[#{self}] on #{path}")
if @block
verify_block(path, opts)
elsif @command.is_a?(Symbol)
@@ -106,15 +112,15 @@ 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)
- # 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 }
+ if @command.include?("%{file}")
+ raise ArgumentError, "The %{file} expansion for verify commands has been removed. Please use %{path} instead."
+ end
+
+ command = @command % { path: path }
interpreter = Chef::GuardInterpreter.for_resource(@parent_resource, command, @command_opts)
- interpreter.evaluate
+ ret = interpreter.evaluate
+ @output = interpreter.output
+ ret
end
def verify_registered_verification(path, opts)
@@ -122,6 +128,16 @@ class Chef
v = verification_class.new(@parent_resource, @command, @command_opts, &@block)
v.verify(path, opts)
end
+
+ def to_s
+ if @block
+ "<Proc>"
+ elsif @command.is_a?(Symbol)
+ "#{@command.inspect} (#{Chef::Resource::File::Verification.lookup(@command).name})"
+ elsif @command.is_a?(String)
+ @command
+ end
+ end
end
end
end
diff --git a/lib/chef/resource/file/verification/systemd_unit.rb b/lib/chef/resource/file/verification/systemd_unit.rb
new file mode 100644
index 0000000000..bfc8881522
--- /dev/null
+++ b/lib/chef/resource/file/verification/systemd_unit.rb
@@ -0,0 +1,68 @@
+#
+# Author:: Mal Graty (<mal.graty@googlemail.com>)
+# Copyright:: Copyright (c) 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_relative "../../../mixin/which"
+
+class Chef
+ class Resource
+ class File
+ class Verification
+
+ #
+ # Systemd provides a binary for verifying the correctness of
+ # unit files. Unfortunately some units have constraints on the
+ # filename meaning that normal verification against temp files
+ # won't work.
+ #
+ # Working around that requires placing a copy of the temp file
+ # in a temp directory, under its real name and running the
+ # verification tool against that file.
+ #
+
+ class SystemdUnit < Chef::Resource::File::Verification
+ include Chef::Mixin::Which
+
+ provides :systemd_unit
+
+ def initialize(parent_resource, command, opts, &block)
+ super
+ @command = systemd_analyze_cmd
+ end
+
+ def verify(path, opts = {})
+ return true unless systemd_analyze_path
+
+ Dir.mktmpdir("chef-systemd-unit") do |dir|
+ temp = "#{dir}/#{::File.basename(@parent_resource.path)}"
+ ::FileUtils.cp(path, temp)
+ verify_command(temp, opts)
+ end
+ end
+
+ def systemd_analyze_cmd
+ @systemd_analyze_cmd ||= "#{systemd_analyze_path} verify %{path}"
+ end
+
+ def systemd_analyze_path
+ @systemd_analyze_path ||= which("systemd-analyze")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/freebsd_package.rb b/lib/chef/resource/freebsd_package.rb
index a94dd0a928..081adaf702 100644
--- a/lib/chef/resource/freebsd_package.rb
+++ b/lib/chef/resource/freebsd_package.rb
@@ -1,7 +1,7 @@
#
# Authors:: AJ Christensen (<aj@chef.io>)
# Richard Manyanza (<liseki@nyikacraftsmen.com>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# Copyright:: Copyright 2014-2016, Richard Manyanza.
# License:: Apache License, Version 2.0
#
@@ -18,43 +18,34 @@
# 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_relative "package"
+require_relative "../provider/package/freebsd/port"
+require_relative "../provider/package/freebsd/pkgng"
class Chef
class Resource
class FreebsdPackage < Chef::Resource::Package
- include Chef::Mixin::ShellOut
-
- resource_name :freebsd_package
+ unified_mode true
+ provides :freebsd_package
provides :package, platform: "freebsd"
+ description "Use the **freebsd_package** resource to manage packages for the FreeBSD platform."
+
+ # make sure we assign the appropriate underlying providers based on what
+ # package managers exist on this FreeBSD system or the source of the package
+ #
+ # @return [void]
def after_created
assign_provider
end
- def supports_pkgng?
- ships_with_pkgng? || !!shell_out!("make -V WITH_PKGNG", :env => nil).stdout.match(/yes/i)
- end
-
private
- def ships_with_pkgng?
- # It was not until __FreeBSD_version 1000017 that pkgng became
- # the default binary package manager. See '/usr/ports/Mk/bsd.port.mk'.
- node[:os_version].to_i >= 1000017
- end
-
def assign_provider
- @provider = if source.to_s =~ /^ports$/i
+ @provider = if /^ports$/i.match?(source.to_s)
Chef::Provider::Package::Freebsd::Port
- elsif supports_pkgng?
- Chef::Provider::Package::Freebsd::Pkgng
else
- Chef::Provider::Package::Freebsd::Pkg
+ Chef::Provider::Package::Freebsd::Pkgng
end
end
end
diff --git a/lib/chef/resource/gem_package.rb b/lib/chef/resource/gem_package.rb
index e095115356..a3ad5f614b 100644
--- a/lib/chef/resource/gem_package.rb
+++ b/lib/chef/resource/gem_package.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,26 +16,85 @@
# limitations under the License.
#
-require "chef/resource/package"
+require_relative "package"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Resource
class GemPackage < Chef::Resource::Package
- resource_name :gem_package
+ unified_mode true
+ provides :gem_package
- property :source, [ String, Array ]
- property :clear_sources, [ true, false ], default: false, desired_state: false
- # Sets a custom gem_binary to run for gem commands.
- property :gem_binary, String, desired_state: false
+ description <<~DESC
+ Use the **gem_package** resource to manage gem packages that are only included in recipes.
+ When a gem is installed from a local file, it must be added to the node using the **remote_file** or **cookbook_file** resources.
- ##
- # Options for the gem install, either a Hash or a String. When a hash is
- # given, the options are passed to Gem::DependencyInstaller.new, and the
- # 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.
- property :options, [ String, Hash, nil ], desired_state: false
+ Note: The **gem_package** resource must be specified as `gem_package` and cannot be shortened to `package` in a recipe.
+ Warning: The **chef_gem** and **gem_package** resources are both used to install Ruby gems. For any machine on which #{ChefUtils::Dist::Infra::PRODUCT} is
+ installed, there are two instances of Ruby. One is the standard, system-wide instance of Ruby and the other is a dedicated instance that is
+ available only to #{ChefUtils::Dist::Infra::PRODUCT}.
+ Use the **chef_gem** resource to install gems into the instance of Ruby that is dedicated to #{ChefUtils::Dist::Infra::PRODUCT}.
+ Use the **gem_package** resource to install all other gems (i.e. install gems system-wide).
+ DESC
+
+ examples <<~EXAMPLES
+ The following examples demonstrate various approaches for using the **gem_package** resource in recipes:
+
+ **Install a gem file from the local file system**
+
+ ```ruby
+ gem_package 'loofah' do
+ source '/tmp/loofah-2.7.0.gem'
+ action :install
+ end
+ ```
+
+ **Use the `ignore_failure` common attribute**
+
+ ```ruby
+ gem_package 'syntax' do
+ action :install
+ ignore_failure true
+ end
+ ```
+ EXAMPLES
+
+ property :package_name, String,
+ description: "An optional property to set the package name if it differs from the resource block's name.",
+ identity: true
+
+ property :version, String,
+ description: "The version of a package to be installed or upgraded."
+
+ # the source can either be a path to a package source like:
+ # source /var/tmp/mygem-1.2.3.4.gem
+ # or it can be a url rubygems source like:
+ # https://rubygems.org
+ # the default has to be nil in order for the magical wiring up of the name property to
+ # the source pathname to work correctly.
+ #
+ # we don't do coercions here because its all a bit too complicated
+ #
+ # FIXME? the array form of installing paths most likely does not work?
+ #
+ property :source, [ String, Array ],
+ description: "Optional. The URL, or list of URLs, at which the gem package is located. This list is added to the source configured in `Chef::Config[:rubygems_url]` (see also include_default_source) to construct the complete list of rubygems sources. Users in an 'airgapped' environment should set Chef::Config[:rubygems_url] to their local RubyGems mirror."
+
+ property :clear_sources, [ TrueClass, FalseClass, nil ],
+ description: "Set to `true` to download a gem from the path specified by the `source` property (and not from RubyGems).",
+ default: lazy { Chef::Config[:clear_gem_sources] }, desired_state: false
+
+ property :gem_binary, String, desired_state: false,
+ description: "The path of a gem binary to use for the installation. By default, the same version of Ruby that is used by #{ChefUtils::Dist::Infra::PRODUCT} will be used."
+
+ property :include_default_source, [ TrueClass, FalseClass, nil ],
+ description: "Set to `false` to not include `Chef::Config[:rubygems_url]` in the sources.",
+ default: nil, introduced: "13.0"
+
+ property :options, [ String, Hash, Array, nil ],
+ description: "Options for the gem install, either a Hash or a String. When a hash is given, the options are passed to `Gem::DependencyInstaller.new`, and the 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.",
+ desired_state: false
end
end
end
diff --git a/lib/chef/resource/group.rb b/lib/chef/resource/group.rb
index d3a4a1ce89..3a129592d0 100644
--- a/lib/chef/resource/group.rb
+++ b/lib/chef/resource/group.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,83 +20,45 @@
class Chef
class Resource
class Group < Chef::Resource
+ unified_mode true
+ state_attrs :members
- identity_attr :group_name
+ description "Use the **group** resource to manage a local group."
- state_attrs :members
+ provides :group
allowed_actions :create, :remove, :modify, :manage
default_action :create
- def initialize(name, run_context = nil)
- super
- @group_name = name
- @gid = nil
- @members = []
- @excluded_members = []
- @append = false
- @non_unique = false
- end
+ property :group_name, String,
+ name_property: true,
+ description: "The name of the group."
- def group_name(arg = nil)
- set_or_return(
- :group_name,
- arg,
- :kind_of => [ String ]
- )
- end
+ property :gid, [ String, Integer ],
+ description: "The identifier for the group."
- def gid(arg = nil)
- set_or_return(
- :gid,
- arg,
- :kind_of => [ String, Integer ]
- )
- end
+ property :members, [String, Array], default: lazy { [] },
+ coerce: proc { |arg| arg.is_a?(String) ? arg.split(/\s*,\s*/) : arg },
+ description: "Which users should be set or appended to a group. When more than one group member is identified, the list of members should be an array: members ['user1', 'user2']."
- def members(arg = nil)
- converted_members = arg.is_a?(String) ? arg.split(",") : arg
- set_or_return(
- :members,
- converted_members,
- :kind_of => [ Array ]
- )
- end
+ property :excluded_members, [String, Array], default: lazy { [] },
+ coerce: proc { |arg| arg.is_a?(String) ? arg.split(/\s*,\s*/) : arg },
+ description: "Remove users from a group. May only be used when append is set to true."
- alias_method :users, :members
+ property :append, [ TrueClass, FalseClass ], default: false,
+ description: "How members should be appended and/or removed from a group. When true, members are appended and excluded_members are removed. When false, group members are reset to the value of the members property."
- def excluded_members(arg = nil)
- converted_members = arg.is_a?(String) ? arg.split(",") : arg
- set_or_return(
- :excluded_members,
- converted_members,
- :kind_of => [ Array ]
- )
- end
+ property :system, [ TrueClass, FalseClass ], default: false,
+ description: "Set if a group belongs to a system group. Set to true if the group belongs to a system group."
- def append(arg = nil)
- set_or_return(
- :append,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
+ property :non_unique, [ TrueClass, FalseClass ], default: false,
+ description: "Allow gid duplication. May only be used with the Groupadd provider."
- def system(arg = nil)
- set_or_return(
- :system,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
+ property :comment, String,
+ introduced: "14.9",
+ description: "Specifies a comment to associate with the local group."
- def non_unique(arg = nil)
- set_or_return(
- :non_unique,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
+ alias_method :users, :members
end
end
end
diff --git a/lib/chef/resource/helpers/cron_validations.rb b/lib/chef/resource/helpers/cron_validations.rb
new file mode 100644
index 0000000000..60861be617
--- /dev/null
+++ b/lib/chef/resource/helpers/cron_validations.rb
@@ -0,0 +1,101 @@
+#
+# Copyright:: Copyright (c) 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 ResourceHelpers
+ # a collection of methods for validating cron times. Used in the various cron-like resources
+ module CronValidations
+ # validate a provided value is between two other provided values
+ # we also allow * as a valid input
+ # @param spec the value to validate
+ # @param min the lowest value allowed
+ # @param max the highest value allowed
+ # @return [Boolean] valid or not?
+ def validate_numeric(spec, min, max)
+ return true if spec == "*"
+
+ if spec.respond_to? :to_int
+ return spec >= min && spec <= max
+ end
+
+ # Lists of individual values, ranges, and step values all share the validity range for type
+ spec.split(%r{\/|-|,}).each do |x|
+ next if x == "*"
+ return false unless /^\d+$/.match?(x)
+
+ x = x.to_i
+ return false unless x >= min && x <= max
+ end
+ true
+ end
+
+ # validate the provided month value to be jan - dec, 1 - 12, or *
+ # @param spec the value to validate
+ # @return [Boolean] valid or not?
+ def validate_month(spec)
+ return true if spec == "*"
+
+ if spec.respond_to? :to_int
+ validate_numeric(spec, 1, 12)
+ elsif spec.respond_to? :to_str
+ # Named abbreviations are permitted but not as part of a range or with stepping
+ return true if %w{jan feb mar apr may jun jul aug sep oct nov dec}.include? spec.downcase
+
+ # 1-12 are legal for months
+ validate_numeric(spec, 1, 12)
+ else
+ false
+ end
+ end
+
+ # validate the provided day of the week is sun-sat, sunday-saturday, 0-7, or *
+ # Added crontab param to check cron resource
+ # @param spec the value to validate
+ # @return [Boolean] valid or not?
+ def validate_dow(spec)
+ spec = spec.to_s
+ spec == "*" ||
+ validate_numeric(spec, 0, 7) ||
+ %w{sun mon tue wed thu fri sat}.include?(spec.downcase) ||
+ %w{sunday monday tuesday wednesday thursday friday saturday}.include?(spec.downcase)
+ end
+
+ # validate the day of the month is 1-31
+ # @param spec the value to validate
+ # @return [Boolean] valid or not?
+ def validate_day(spec)
+ validate_numeric(spec, 1, 31)
+ end
+
+ # validate the hour is 0-23
+ # @param spec the value to validate
+ # @return [Boolean] valid or not?
+ def validate_hour(spec)
+ validate_numeric(spec, 0, 23)
+ end
+
+ # validate the minute is 0-59
+ # @param spec the value to validate
+ # @return [Boolean] valid or not?
+ def validate_minute(spec)
+ validate_numeric(spec, 0, 59)
+ end
+
+ extend self
+ end
+ end
+end
diff --git a/lib/chef/resource/homebrew_cask.rb b/lib/chef/resource/homebrew_cask.rb
new file mode 100644
index 0000000000..4c68afaab0
--- /dev/null
+++ b/lib/chef/resource/homebrew_cask.rb
@@ -0,0 +1,104 @@
+#
+# Author:: Joshua Timberman (<jtimberman@chef.io>)
+# Author:: Graeme Mathieson (<mathie@woss.name>)
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+require_relative "../mixin/homebrew_user"
+
+class Chef
+ class Resource
+ class HomebrewCask < Chef::Resource
+ unified_mode true
+
+ provides(:homebrew_cask) { true }
+
+ description "Use the **homebrew_cask** resource to install binaries distributed via the Homebrew package manager."
+ introduced "14.0"
+
+ include Chef::Mixin::HomebrewUser
+
+ property :cask_name, String,
+ description: "An optional property to set the cask name if it differs from the resource block's name.",
+ regex: %r{^[\w/-]+$},
+ validation_message: "The provided Homebrew cask name is not valid. Cask names can contain alphanumeric characters, _, -, or / only!",
+ name_property: true
+
+ property :options, String,
+ description: "Options to pass to the brew command during installation."
+
+ property :install_cask, [TrueClass, FalseClass],
+ description: "Automatically install the Homebrew cask tap, if necessary.",
+ default: true
+
+ property :homebrew_path, String,
+ description: "The path to the homebrew binary.",
+ default: "/usr/local/bin/brew"
+
+ property :owner, [String, Integer],
+ description: "The owner of the Homebrew installation.",
+ default: lazy { find_homebrew_username }
+
+ action :install do
+ description "Install an application packaged as a Homebrew cask."
+
+ homebrew_tap "homebrew/cask" if new_resource.install_cask
+
+ unless casked?
+ converge_by("install cask #{new_resource.cask_name} #{new_resource.options}") do
+ shell_out!("#{new_resource.homebrew_path} install --cask #{new_resource.cask_name} #{new_resource.options}",
+ user: new_resource.owner,
+ env: { "HOME" => ::Dir.home(new_resource.owner), "USER" => new_resource.owner },
+ cwd: ::Dir.home(new_resource.owner))
+ end
+ end
+ end
+
+ action :remove do
+ description "Remove an application packaged as a Homebrew cask."
+
+ homebrew_tap "homebrew/cask" if new_resource.install_cask
+
+ if casked?
+ converge_by("uninstall cask #{new_resource.cask_name}") do
+ shell_out!("#{new_resource.homebrew_path} uninstall --cask #{new_resource.cask_name}",
+ user: new_resource.owner,
+ env: { "HOME" => ::Dir.home(new_resource.owner), "USER" => new_resource.owner },
+ cwd: ::Dir.home(new_resource.owner))
+ end
+ end
+ end
+
+ action_class do
+ alias_method :action_cask, :action_install
+ alias_method :action_uncask, :action_remove
+ alias_method :action_uninstall, :action_remove
+
+ # Is the desired cask already casked?
+ #
+ # @return [Boolean]
+ def casked?
+ unscoped_name = new_resource.cask_name.split("/").last
+ shell_out!("#{new_resource.homebrew_path} list --cask 2>/dev/null",
+ user: new_resource.owner,
+ env: { "HOME" => ::Dir.home(new_resource.owner), "USER" => new_resource.owner },
+ cwd: ::Dir.home(new_resource.owner)).stdout.split.include?(unscoped_name)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/homebrew_package.rb b/lib/chef/resource/homebrew_package.rb
index c2d0a65c5b..3874622005 100644
--- a/lib/chef/resource/homebrew_package.rb
+++ b/lib/chef/resource/homebrew_package.rb
@@ -2,8 +2,7 @@
# Author:: Joshua Timberman (<joshua@chef.io>)
# Author:: Graeme Mathieson (<mathie@woss.name>)
#
-# Copyright 2011-2016, Chef Software Inc.
-# Copyright 2014-2016, Chef Software, Inc <legal@chef.io>
+# Copyright:: Copyright (c) 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.
@@ -18,16 +17,52 @@
# limitations under the License.
#
-require "chef/provider/package"
-require "chef/resource/package"
+require_relative "../provider/package"
+require_relative "package"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Resource
class HomebrewPackage < Chef::Resource::Package
- resource_name :homebrew_package
+ unified_mode true
+
+ provides :homebrew_package
provides :package, os: "darwin"
- property :homebrew_user, [ String, Integer ]
+ description "Use the **homebrew_package** resource to manage packages for the macOS platform. Note: Starting with #{ChefUtils::Dist::Infra::PRODUCT} 16 the homebrew resource now accepts an array of packages for installing multiple packages at once."
+ introduced "12.0"
+ examples <<~DOC
+ **Install a package**:
+
+ ```ruby
+ homebrew_package 'git'
+ ```
+
+ **Install multiple packages at once**:
+
+ ```ruby
+ homebrew_package %w(git fish ruby)
+ ```
+
+ **Specify the Homebrew user with a UUID**
+
+ ```ruby
+ homebrew_package 'git' do
+ homebrew_user 1001
+ end
+ ```
+
+ **Specify the Homebrew user with a string**:
+
+ ```ruby
+ homebrew_package 'vim' do
+ homebrew_user 'user1'
+ end
+ ```
+ DOC
+
+ property :homebrew_user, [ String, Integer ],
+ description: "The name or uid of the Homebrew owner to be used by #{ChefUtils::Dist::Infra::PRODUCT} when executing a command."
end
end
diff --git a/lib/chef/resource/homebrew_tap.rb b/lib/chef/resource/homebrew_tap.rb
new file mode 100644
index 0000000000..937a9ab420
--- /dev/null
+++ b/lib/chef/resource/homebrew_tap.rb
@@ -0,0 +1,91 @@
+#
+# Author:: Joshua Timberman (<jtimberman@chef.io>)
+# Author:: Graeme Mathieson (<mathie@woss.name>)
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+require_relative "../mixin/homebrew_user"
+
+class Chef
+ class Resource
+ class HomebrewTap < Chef::Resource
+ unified_mode true
+
+ provides(:homebrew_tap) { true }
+
+ description "Use the **homebrew_tap** resource to add additional formula repositories to the Homebrew package manager."
+ introduced "14.0"
+
+ include Chef::Mixin::HomebrewUser
+
+ property :tap_name, String,
+ description: "An optional property to set the tap name if it differs from the resource block's name.",
+ validation_message: "Homebrew tap names must be in the form REPO/TAP format!",
+ regex: %r{^[\w-]+(?:\/[\w-]+)+$},
+ name_property: true
+
+ property :url, String,
+ description: "The URL of the tap."
+
+ property :full, [TrueClass, FalseClass],
+ description: "Perform a full clone on the tap, as opposed to a shallow clone.",
+ default: false
+
+ property :homebrew_path, String,
+ description: "The path to the Homebrew binary.",
+ default: "/usr/local/bin/brew"
+
+ property :owner, String,
+ description: "The owner of the Homebrew installation.",
+ default: lazy { find_homebrew_username }
+
+ action :tap do
+ description "Add a Homebrew tap."
+
+ unless tapped?(new_resource.tap_name)
+ converge_by("tap #{new_resource.tap_name}") do
+ shell_out!("#{new_resource.homebrew_path} tap #{new_resource.full ? "--full" : ""} #{new_resource.tap_name} #{new_resource.url || ""}",
+ user: new_resource.owner,
+ env: { "HOME" => ::Dir.home(new_resource.owner), "USER" => new_resource.owner },
+ cwd: ::Dir.home(new_resource.owner))
+ end
+ end
+ end
+
+ action :untap do
+ description "Remove a Homebrew tap."
+
+ if tapped?(new_resource.tap_name)
+ converge_by("untap #{new_resource.tap_name}") do
+ shell_out!("#{new_resource.homebrew_path} untap #{new_resource.tap_name}",
+ user: new_resource.owner,
+ env: { "HOME" => ::Dir.home(new_resource.owner), "USER" => new_resource.owner },
+ cwd: ::Dir.home(new_resource.owner))
+ end
+ end
+ end
+
+ # Is the passed tap already tapped
+ #
+ # @return [Boolean]
+ def tapped?(name)
+ tap_dir = name.gsub("/", "/homebrew-")
+ ::File.directory?("/usr/local/Homebrew/Library/Taps/#{tap_dir}")
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/homebrew_update.rb b/lib/chef/resource/homebrew_update.rb
new file mode 100644
index 0000000000..27b352bfb6
--- /dev/null
+++ b/lib/chef/resource/homebrew_update.rb
@@ -0,0 +1,110 @@
+#
+# Author:: Joshua Timberman (<jtimberman@chef.io>)
+# Author:: Dan Webb (<dan@webb-agile-solutions.ltd>)
+#
+# Copyright:: Copyright (c) Chef Software Inc.
+# Copyright:: Copyright (c) Webb Agile Solutions Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../resource"
+require_relative "../mixin/homebrew_user"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Resource
+ class HomebrewUpdate < Chef::Resource
+ include Chef::Mixin::HomebrewUser
+
+ unified_mode true
+
+ provides(:homebrew_update) { true }
+
+ description "Use the **homebrew_update** resource to manage Homebrew repository updates on macOS."
+ introduced "16.2"
+ examples <<~DOC
+ **Update the homebrew repository data at a specified interval**:
+ ```ruby
+ homebrew_update 'all platforms' do
+ frequency 86400
+ action :periodic
+ end
+ ```
+ **Update the Homebrew repository at the start of a #{ChefUtils::Dist::Infra::PRODUCT} run**:
+ ```ruby
+ homebrew_update 'update'
+ ```
+ DOC
+
+ # allow bare homebrew_update with no name
+ property :name, String, default: ""
+
+ property :frequency, Integer,
+ description: "Determines how frequently (in seconds) Homebrew updates are made. Use this property when the `:periodic` action is specified.",
+ default: 86_400
+
+ default_action :periodic
+ allowed_actions :update, :periodic
+
+ action_class do
+ BREW_STAMP_DIR = "/var/lib/homebrew/periodic".freeze
+ BREW_STAMP = "#{BREW_STAMP_DIR}/update-success-stamp".freeze
+
+ # Determines whether we need to run `homebrew update`
+ #
+ # @return [Boolean]
+ def brew_up_to_date?
+ ::File.exist?(BREW_STAMP) &&
+ ::File.mtime(BREW_STAMP) > Time.now - new_resource.frequency
+ end
+
+ def do_update
+ directory BREW_STAMP_DIR do
+ recursive true
+ end
+
+ file BREW_STAMP do
+ content "BREW::Update::Post-Invoke-Success\n"
+ action :create_if_missing
+ end
+
+ execute "brew update" do
+ command %w{brew update}
+ default_env true
+ user find_homebrew_uid
+ notifies :touch, "file[#{BREW_STAMP}]", :immediately
+ end
+ end
+ end
+
+ action :periodic do
+ return unless macos?
+
+ unless brew_up_to_date?
+ converge_by "update new lists of packages" do
+ do_update
+ end
+ end
+ end
+
+ action :update do
+ return unless macos?
+
+ converge_by "force update new lists of packages" do
+ do_update
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/hostname.rb b/lib/chef/resource/hostname.rb
new file mode 100644
index 0000000000..e63c0be54e
--- /dev/null
+++ b/lib/chef/resource/hostname.rb
@@ -0,0 +1,260 @@
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Resource
+ # Sets the hostname and updates /etc/hosts on *nix systems
+ # @since 14.0.0
+ class Hostname < Chef::Resource
+ unified_mode true
+
+ provides :hostname
+
+ description "Use the **hostname** resource to set the system's hostname, configure hostname and hosts config file, and re-run the Ohai hostname plugin so the hostname will be available in subsequent cookbooks."
+ introduced "14.0"
+ examples <<~DOC
+ **Set the hostname using the IP address, as detected by Ohai**:
+
+ ```ruby
+ hostname 'example'
+ ```
+
+ **Manually specify the hostname and IP address**:
+
+ ```ruby
+ hostname 'statically_configured_host' do
+ hostname 'example'
+ ipaddress '198.51.100.2'
+ end
+ ```
+ DOC
+
+ property :hostname, String,
+ description: "An optional property to set the hostname if it differs from the resource block's name.",
+ name_property: true
+
+ property :ipaddress, String,
+ description: "The IP address to use when configuring the hosts file.",
+ default: lazy { node["ipaddress"] }, default_description: "The node's IP address as determined by Ohai."
+
+ property :aliases, [ Array, nil ],
+ description: "An array of hostname aliases to use when configuring the hosts file.",
+ default: nil
+
+ # override compile_time property to be true by default
+ property :compile_time, [ TrueClass, FalseClass ],
+ description: "Determines whether or not the resource should be run at compile time.",
+ default: true, desired_state: false
+
+ property :windows_reboot, [ TrueClass, FalseClass ],
+ description: "Determines whether or not Windows should be reboot after changing the hostname, as this is required for the change to take effect.",
+ default: true
+
+ action_class do
+ def append_replacing_matching_lines(path, regex, string)
+ text = IO.read(path).split("\n")
+ text.reject! { |s| s =~ regex }
+ text += [ string ]
+ file path do
+ content text.join("\n") + "\n"
+ owner "root"
+ group node["root_group"]
+ mode "0644"
+ not_if { IO.read(path).split("\n").include?(string) }
+ end
+ end
+
+ # read in the xml file used by Ec2ConfigService and update the Ec2SetComputerName
+ # setting to disable updating the computer name so we don't revert our change on reboot
+ # @return [String]
+ def updated_ec2_config_xml
+ begin
+ require "rexml/document" unless defined?(REXML::Document)
+ config = REXML::Document.new(::File.read(WINDOWS_EC2_CONFIG))
+ # find an element named State with a sibling element whose value is Ec2SetComputerName
+ REXML::XPath.each(config, "//Plugin/State[../Name/text() = 'Ec2SetComputerName']") do |element|
+ element.text = "Disabled"
+ end
+ rescue
+ return ""
+ end
+ config.to_s
+ end
+ end
+
+ action :set do
+ description "Sets the node's hostname."
+
+ if !windows?
+ ohai "reload hostname" do
+ plugin "hostname"
+ action :nothing
+ end
+
+ # set the hostname via /bin/hostname
+ execute "set hostname to #{new_resource.hostname}" do
+ command "/bin/hostname #{new_resource.hostname}"
+ not_if { shell_out!("hostname").stdout.chomp == new_resource.hostname }
+ notifies :reload, "ohai[reload hostname]"
+ end
+
+ # make sure node['fqdn'] resolves via /etc/hosts
+ unless new_resource.ipaddress.nil?
+ newline = "#{new_resource.ipaddress} #{new_resource.hostname}"
+ newline << " #{new_resource.aliases.join(" ")}" if new_resource.aliases && !new_resource.aliases.empty?
+ newline << " #{new_resource.hostname[/[^\.]*/]}"
+ r = append_replacing_matching_lines("/etc/hosts", /^#{new_resource.ipaddress}\s+|\s+#{new_resource.hostname}\s+/, newline)
+ r.atomic_update false if docker?
+ r.notifies :reload, "ohai[reload hostname]"
+ end
+
+ # setup the hostname to persist on a reboot
+ case
+ when darwin?
+ # darwin
+ execute "set HostName via scutil" do
+ command "/usr/sbin/scutil --set HostName #{new_resource.hostname}"
+ not_if { shell_out("/usr/sbin/scutil --get HostName").stdout.chomp == new_resource.hostname }
+ notifies :reload, "ohai[reload hostname]"
+ end
+ execute "set ComputerName via scutil" do
+ command "/usr/sbin/scutil --set ComputerName #{new_resource.hostname}"
+ not_if { shell_out("/usr/sbin/scutil --get ComputerName").stdout.chomp == new_resource.hostname }
+ notifies :reload, "ohai[reload hostname]"
+ end
+ shortname = new_resource.hostname[/[^\.]*/]
+ execute "set LocalHostName via scutil" do
+ command "/usr/sbin/scutil --set LocalHostName #{shortname}"
+ not_if { shell_out("/usr/sbin/scutil --get LocalHostName").stdout.chomp == shortname }
+ notifies :reload, "ohai[reload hostname]"
+ end
+ when linux?
+ case
+ when ::File.exist?("/usr/bin/hostnamectl") && !docker?
+ # use hostnamectl whenever we find it on linux (as systemd takes over the world)
+ # this must come before other methods like /etc/hostname and /etc/sysconfig/network
+ execute "hostnamectl set-hostname #{new_resource.hostname}" do
+ notifies :reload, "ohai[reload hostname]"
+ not_if { shell_out!("hostnamectl status", returns: [0, 1]).stdout =~ /Static hostname:\s*#{new_resource.hostname}\s*$/ }
+ end
+ when ::File.exist?("/etc/hostname")
+ # debian family uses /etc/hostname
+ # arch also uses /etc/hostname
+ # the "platform: iox_xr, platform_family: wrlinux, os: linux" platform also hits this
+ # the "platform: nexus, platform_family: wrlinux, os: linux" platform also hits this
+ # this is also fallback for any linux systemd host in a docker container (where /usr/bin/hostnamectl will fail)
+ file "/etc/hostname" do
+ atomic_update false if docker?
+ content "#{new_resource.hostname}\n"
+ owner "root"
+ group node["root_group"]
+ mode "0644"
+ end
+ when ::File.file?("/etc/sysconfig/network")
+ # older non-systemd RHEL/Fedora derived
+ append_replacing_matching_lines("/etc/sysconfig/network", /^HOSTNAME\s*=/, "HOSTNAME=#{new_resource.hostname}")
+ when ::File.exist?("/etc/HOSTNAME")
+ # SuSE/openSUSE uses /etc/HOSTNAME
+ file "/etc/HOSTNAME" do
+ content "#{new_resource.hostname}\n"
+ owner "root"
+ group node["root_group"]
+ mode "0644"
+ end
+ when ::File.exist?("/etc/conf.d/hostname")
+ # Gentoo
+ file "/etc/conf.d/hostname" do
+ content "hostname=\"#{new_resource.hostname}\"\n"
+ owner "root"
+ group node["root_group"]
+ mode "0644"
+ end
+ else
+ # This is a failsafe for all other linux distributions where we set the hostname
+ # via /etc/sysctl.conf on reboot. This may get into a fight with other cookbooks
+ # that manage sysctls on linux.
+ append_replacing_matching_lines("/etc/sysctl.conf", /^\s+kernel\.hostname\s+=/, "kernel.hostname=#{new_resource.hostname}")
+ end
+ when ::File.exist?("/etc/rc.conf")
+ # *BSD systems with /etc/rc.conf + /etc/myname
+ append_replacing_matching_lines("/etc/rc.conf", /^\s+hostname\s+=/, "hostname=#{new_resource.hostname}")
+
+ file "/etc/myname" do
+ content "#{new_resource.hostname}\n"
+ owner "root"
+ group node["root_group"]
+ mode "0644"
+ end
+ when ::File.exist?("/usr/sbin/svccfg") # solaris 5.11
+ execute "svccfg -s system/identity:node setprop config/nodename=\'#{new_resource.hostname}\'" do
+ notifies :run, "execute[svcadm refresh]", :immediately
+ notifies :run, "execute[svcadm restart]", :immediately
+ not_if { shell_out!("svccfg -s system/identity:node listprop config/nodename").stdout.chomp =~ %r{config/nodename\s+astring\s+#{new_resource.hostname}} }
+ end
+ execute "svcadm refresh" do
+ command "svcadm refresh system/identity:node"
+ action :nothing
+ end
+ execute "svcadm restart" do
+ command "svcadm restart system/identity:node"
+ action :nothing
+ end
+ else
+ raise "Do not know how to set hostname on os #{node["os"]}, platform #{node["platform"]},"\
+ "platform_version #{node["platform_version"]}, platform_family #{node["platform_family"]}"
+ end
+
+ else # windows
+ WINDOWS_EC2_CONFIG = 'C:\Program Files\Amazon\Ec2ConfigService\Settings\config.xml'.freeze
+
+ raise "Windows hostnames cannot contain a period." if new_resource.hostname.include?(".")
+
+ # suppress EC2 config service from setting our hostname
+ if ::File.exist?(WINDOWS_EC2_CONFIG)
+ xml_contents = updated_ec2_config_xml
+ if xml_contents.empty?
+ Chef::Log.warn('Unable to properly parse and update C:\Program Files\Amazon\Ec2ConfigService\Settings\config.xml contents. Skipping file update.')
+ else
+ file WINDOWS_EC2_CONFIG do
+ content xml_contents
+ end
+ end
+ end
+
+ unless Socket.gethostbyname(Socket.gethostname).first == new_resource.hostname
+ converge_by "set hostname to #{new_resource.hostname}" do
+ powershell_exec! <<~EOH
+ $sysInfo = Get-WmiObject -Class Win32_ComputerSystem
+ $sysInfo.Rename("#{new_resource.hostname}")
+ EOH
+ end
+
+ # reboot because $windows
+ reboot "setting hostname" do
+ reason "#{ChefUtils::Dist::Infra::PRODUCT} updated system hostname"
+ only_if { new_resource.windows_reboot }
+ action :request_reboot
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/http_request.rb b/lib/chef/resource/http_request.rb
index fcc48470bc..f53d3e731f 100644
--- a/lib/chef/resource/http_request.rb
+++ b/lib/chef/resource/http_request.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,31 +17,29 @@
# limitations under the License.
#
-require "chef/resource"
-require "chef/provider/http_request"
+require_relative "../resource"
class Chef
class Resource
class HttpRequest < Chef::Resource
+ unified_mode true
- identity_attr :url
+ provides :http_request
+
+ description "Use the **http_request** resource to send an HTTP request (`GET`, `PUT`, `POST`, `DELETE`, `HEAD`, or `OPTIONS`) with an arbitrary message. This resource is often useful when custom callbacks are necessary."
default_action :get
- allowed_actions :get, :put, :post, :delete, :head, :options
+ allowed_actions :get, :patch, :put, :post, :delete, :head, :options
+
+ property :url, String, identity: true,
+ description: "The URL to which an HTTP request is sent."
+
+ property :headers, Hash, default: lazy { {} },
+ description: "A Hash of custom headers."
def initialize(name, run_context = nil)
super
@message = name
- @url = nil
- @headers = {}
- end
-
- def url(args = nil)
- set_or_return(
- :url,
- args,
- :kind_of => String
- )
end
def message(args = nil, &block)
@@ -49,15 +47,7 @@ class Chef
set_or_return(
:message,
args,
- :kind_of => Object
- )
- end
-
- def headers(args = nil)
- set_or_return(
- :headers,
- args,
- :kind_of => Hash
+ kind_of: Object
)
end
diff --git a/lib/chef/resource/ifconfig.rb b/lib/chef/resource/ifconfig.rb
index fd523d9580..2c5262fa97 100644
--- a/lib/chef/resource/ifconfig.rb
+++ b/lib/chef/resource/ifconfig.rb
@@ -17,131 +17,138 @@
# limitations under the License.
#
-require "chef/resource"
+require_relative "../resource"
class Chef
class Resource
class Ifconfig < Chef::Resource
+ unified_mode true
- identity_attr :device
+ provides :ifconfig
+
+ description "Use the **ifconfig** resource to manage interfaces on Unix and Linux systems. Note: This resource requires the ifconfig binary to be present on the system and may require additional packages to be installed first. On Ubuntu 18.04 or later you will need to install the `ifupdown` package, which disables the built in Netplan functionality. Warning: This resource will not work with Fedora release 33 or later."
+ examples <<~DOC
+ **Configure a network interface with a static IP**
+
+ ```ruby
+ ifconfig '33.33.33.80' do
+ device 'eth1'
+ end
+ ```
+
+ will create the following interface configuration:
+
+ ```
+ iface eth1 inet static
+ address 33.33.33.80
+ ```
+
+ **Configure an interface to use DHCP**
+
+ ```ruby
+ ifconfig 'Set eth1 to DHCP' do
+ device 'eth1'
+ bootproto 'dhcp'
+ end
+ ```
+
+ will create the following interface configuration:
+
+ ```
+ iface eth1 inet dhcp
+ ```
+
+ **Update a static IP address with a boot protocol**
+
+ ```ruby
+ ifconfig "33.33.33.80" do
+ bootproto "dhcp"
+ device "eth1"
+ end
+ ```
+
+ will update the interface configuration from static to dhcp:
+
+ ```
+ iface eth1 inet dhcp
+ address 33.33.33.80
+ ```
+ DOC
state_attrs :inet_addr, :mask
default_action :add
allowed_actions :add, :delete, :enable, :disable
- def initialize(name, run_context = nil)
- super
- @target = name
- @hwaddr = nil
- @mask = nil
- @inet_addr = nil
- @bcast = nil
- @mtu = nil
- @metric = nil
- @device = nil
- @onboot = nil
- @network = nil
- @bootproto = nil
- @onparent = nil
- end
+ property :target, String,
+ name_property: true,
+ description: "The IP address that is to be assigned to the network interface. If not specified we'll use the resource's name."
- def target(arg = nil)
- set_or_return(
- :target,
- arg,
- :kind_of => String
- )
- end
+ property :hwaddr, String,
+ description: "The hardware address for the network interface."
- def device(arg = nil)
- set_or_return(
- :device,
- arg,
- :kind_of => String
- )
- end
+ property :mask, String,
+ description: "The decimal representation of the network mask. For example: `255.255.255.0`."
- def hwaddr(arg = nil)
- set_or_return(
- :hwaddr,
- arg,
- :kind_of => String
- )
- end
+ property :family, String, default: "inet",
+ introduced: "14.0",
+ description: "Networking family option for Debian-based systems; for example: `inet` or `inet6`."
- def inet_addr(arg = nil)
- set_or_return(
- :inet_addr,
- arg,
- :kind_of => String
- )
- end
+ property :inet_addr, String,
+ description: "The Internet host address for the network interface."
- def bcast(arg = nil)
- set_or_return(
- :bcast,
- arg,
- :kind_of => String
- )
- end
+ property :bcast, String,
+ description: "The broadcast address for a network interface. On some platforms this property is not set using ifconfig, but instead is added to the startup configuration file for the network interface."
- def mask(arg = nil)
- set_or_return(
- :mask,
- arg,
- :kind_of => String
- )
- end
+ property :mtu, String,
+ description: "The maximum transmission unit (MTU) for the network interface."
- def mtu(arg = nil)
- set_or_return(
- :mtu,
- arg,
- :kind_of => String
- )
- end
+ property :metric, String,
+ description: "The routing metric for the interface."
- def metric(arg = nil)
- set_or_return(
- :metric,
- arg,
- :kind_of => String
- )
- end
+ property :device, String,
+ identity: true,
+ description: "The network interface to be configured."
- def onboot(arg = nil)
- set_or_return(
- :onboot,
- arg,
- :kind_of => String
- )
- end
+ property :onboot, String,
+ description: "Bring up the network interface on boot."
- def network(arg = nil)
- set_or_return(
- :network,
- arg,
- :kind_of => String
- )
- end
+ property :network, String,
+ description: "The address for the network interface."
- def bootproto(arg = nil)
- set_or_return(
- :bootproto,
- arg,
- :kind_of => String
- )
- end
+ property :bootproto, String,
+ description: "The boot protocol used by a network interface."
- def onparent(arg = nil)
- set_or_return(
- :onparent,
- arg,
- :kind_of => String
- )
- end
- end
+ property :onparent, String,
+ description: "Bring up the network interface when its parent interface is brought up."
+
+ property :ethtool_opts, String,
+ introduced: "13.4",
+ description: "Options to be passed to ethtool(8). For example: `-A eth0 autoneg off rx off tx off`."
+
+ property :bonding_opts, String,
+ introduced: "13.4",
+ description: "Bonding options to pass via `BONDING_OPTS` on RHEL and CentOS. For example: `mode=active-backup miimon=100`."
+
+ property :master, String,
+ introduced: "13.4",
+ description: "Specifies the channel bonding interface to which the Ethernet interface is linked."
+ property :slave, String,
+ introduced: "13.4",
+ description: "When set to `yes`, this device is controlled by the channel bonding interface that is specified via the `master` property."
+
+ property :vlan, String,
+ introduced: "14.4",
+ description: "The VLAN to assign the interface to."
+
+ property :gateway, String,
+ introduced: "14.4",
+ description: "The gateway to use for the interface."
+
+ property :bridge, String,
+ introduced: "16.7",
+ description: "The bridge interface this interface is a member of on Red Hat based systems."
+ end
end
end
diff --git a/lib/chef/resource/ips_package.rb b/lib/chef/resource/ips_package.rb
index 4d2c957e17..a579a5a5a2 100644
--- a/lib/chef/resource/ips_package.rb
+++ b/lib/chef/resource/ips_package.rb
@@ -1,6 +1,6 @@
#
# Author:: Jason Williams (<williamsjj@digitar.com>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,19 +16,31 @@
# limitations under the License.
#
-require "chef/resource/package"
-require "chef/provider/package/ips"
+require_relative "package"
+require_relative "../provider/package/ips"
class Chef
class Resource
class IpsPackage < ::Chef::Resource::Package
- resource_name :ips_package
+ unified_mode true
+
+ provides :ips_package
provides :package, os: "solaris2"
- provides :ips_package, os: "solaris2"
+
+ description "Use the **ips_package** resource to manage packages (using Image Packaging System (IPS)) on the Solaris 11 platform."
allowed_actions :install, :remove, :upgrade
- property :accept_license, [ true, false ], default: false, desired_state: false
+ property :package_name, String,
+ description: "An optional property to set the package name if it differs from the resource block's name.",
+ identity: true
+
+ property :version, String,
+ description: "The version of a package to be installed or upgraded."
+
+ property :accept_license, [TrueClass, FalseClass],
+ default: false, desired_state: false,
+ description: "Accept an end-user license agreement, automatically."
end
end
end
diff --git a/lib/chef/resource/kernel_module.rb b/lib/chef/resource/kernel_module.rb
new file mode 100644
index 0000000000..485511470e
--- /dev/null
+++ b/lib/chef/resource/kernel_module.rb
@@ -0,0 +1,227 @@
+#
+# Resource:: kernel_module
+#
+# The MIT License (MIT)
+#
+# Copyright:: 2016-2018, Shopify Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
+
+require_relative "../resource"
+
+class Chef
+ class Resource
+ class KernelModule < Chef::Resource
+ unified_mode true
+
+ provides :kernel_module
+
+ description "Use the **kernel_module** resource to manage kernel modules on Linux systems. This resource can load, unload, blacklist, disable, install, and uninstall modules."
+ introduced "14.3"
+ examples <<~DOC
+ Install and load a kernel module, and ensure it loads on reboot.
+
+ ```ruby
+ kernel_module 'loop'
+ ```
+
+ Install and load a kernel with a specific set of options, and ensure it loads on reboot. Consult kernel module
+ documentation for specific options that are supported.
+
+ ```ruby
+ kernel_module 'loop' do
+ options [
+ 'max_loop=4',
+ 'max_part=8',
+ ]
+ end
+ ```
+
+ Load a kernel module.
+
+ ```ruby
+ kernel_module 'loop' do
+ action :load
+ end
+ ```
+
+ Unload a kernel module and remove module config, so it doesn't load on reboot.
+
+ ```ruby
+ kernel_module 'loop' do
+ action :uninstall
+ end
+ ```
+
+ Unload kernel module.
+
+ ```ruby
+ kernel_module 'loop' do
+ action :unload
+ end
+ ```
+
+ Blacklist a module from loading.
+
+ ```ruby
+ kernel_module 'loop' do
+ action :blacklist
+ end
+ ```
+
+ Disable a kernel module.
+
+ ```ruby
+ kernel_module 'loop' do
+ action :disable
+ end
+ ```
+ DOC
+
+ property :modname, String,
+ description: "An optional property to set the kernel module name if it differs from the resource block's name.",
+ name_property: true
+
+ property :options, Array,
+ description: "An optional property to set options for the kernel module.",
+ introduced: "15.4"
+
+ property :load_dir, String,
+ description: "The directory to load modules from.",
+ default: "/etc/modules-load.d"
+
+ property :unload_dir, String,
+ description: "The modprobe.d directory.",
+ default: "/etc/modprobe.d"
+
+ action :install do
+ description "Load kernel module, and ensure it loads on reboot."
+
+ with_run_context :root do
+ find_resource(:execute, "update initramfs") do
+ command initramfs_command
+ action :nothing
+ end
+ end
+
+ # create options file before loading the module
+ unless new_resource.options.nil?
+ file "#{new_resource.unload_dir}/options_#{new_resource.modname}.conf" do
+ content "options #{new_resource.modname} #{new_resource.options.join(" ")}\n"
+ end
+ end
+
+ # load the module first before installing
+ action_load
+
+ directory new_resource.load_dir do
+ recursive true
+ end
+
+ file "#{new_resource.load_dir}/#{new_resource.modname}.conf" do
+ content "#{new_resource.modname}\n"
+ notifies :run, "execute[update initramfs]", :delayed
+ end
+ end
+
+ action :uninstall do
+ description "Unload a kernel module and remove module config, so it doesn't load on reboot."
+ with_run_context :root do
+ find_resource(:execute, "update initramfs") do
+ command initramfs_command
+ action :nothing
+ end
+ end
+
+ file "#{new_resource.load_dir}/#{new_resource.modname}.conf" do
+ action :delete
+ notifies :run, "execute[update initramfs]", :delayed
+ end
+
+ file "#{new_resource.unload_dir}/blacklist_#{new_resource.modname}.conf" do
+ action :delete
+ notifies :run, "execute[update initramfs]", :delayed
+ end
+
+ file "#{new_resource.unload_dir}/options_#{new_resource.modname}.conf" do
+ action :delete
+ end
+
+ action_unload
+ end
+
+ action :blacklist do
+ description "Blacklist a kernel module."
+
+ with_run_context :root do
+ find_resource(:execute, "update initramfs") do
+ command initramfs_command
+ action :nothing
+ end
+ end
+
+ file "#{new_resource.unload_dir}/blacklist_#{new_resource.modname}.conf" do
+ content "blacklist #{new_resource.modname}"
+ notifies :run, "execute[update initramfs]", :delayed
+ end
+
+ action_unload
+ end
+
+ action :disable do
+ description "Disable a kernel module."
+
+ with_run_context :root do
+ find_resource(:execute, "update initramfs") do
+ command initramfs_command
+ action :nothing
+ end
+ end
+
+ file "#{new_resource.unload_dir}/disable_#{new_resource.modname}.conf" do
+ content "install #{new_resource.modname} /bin/false"
+ notifies :run, "execute[update initramfs]", :delayed
+ end
+
+ action_unload
+ end
+
+ action :load do
+ description "Load a kernel module."
+
+ unless module_loaded?
+ converge_by("load kernel module #{new_resource.modname}") do
+ shell_out!("modprobe #{new_resource.modname}")
+ end
+ end
+ end
+
+ action :unload do
+ description "Unload kernel module."
+
+ if module_loaded?
+ converge_by("unload kernel module #{new_resource.modname}") do
+ shell_out!("modprobe -r #{new_resource.modname}")
+ end
+ end
+ end
+
+ action_class do
+ # determine the correct command to regen the initramfs based on platform
+ # @return [String]
+ def initramfs_command
+ if platform_family?("debian")
+ "update-initramfs -u"
+ else
+ "dracut -f"
+ end
+ end
+
+ # see if the module is listed in /proc/modules or not
+ # @return [Boolean]
+ def module_loaded?
+ /^#{new_resource.modname}/.match?(::File.read("/proc/modules"))
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/ksh.rb b/lib/chef/resource/ksh.rb
index 3097156329..28d71970e7 100644
--- a/lib/chef/resource/ksh.rb
+++ b/lib/chef/resource/ksh.rb
@@ -1,6 +1,6 @@
#
# Author:: Nolan Davidson (<nolan.davidson@gmail.com>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,11 +16,22 @@
# limitations under the License.
#
-require "chef/resource/script"
+require_relative "script"
class Chef
class Resource
class Ksh < Chef::Resource::Script
+ unified_mode true
+
+ provides :ksh
+
+ description "Use the **ksh** resource to execute scripts using the Korn shell (ksh)"\
+ " interpreter. This resource may also use any of the actions and properties"\
+ " that are available to the **execute** resource. Commands that are executed"\
+ " with this resource are (by their nature) not idempotent, as they are"\
+ " typically unique to the environment in which they are run. Use `not_if`"\
+ " and `only_if` to guard this resource for idempotence."
+ introduced "12.6"
def initialize(name, run_context = nil)
super
diff --git a/lib/chef/resource/launchd.rb b/lib/chef/resource/launchd.rb
index 8dca90ef0e..c8e3d93afc 100644
--- a/lib/chef/resource/launchd.rb
+++ b/lib/chef/resource/launchd.rb
@@ -16,89 +16,242 @@
# limitations under the License.
#
-require "chef/resource"
-require "chef/provider/launchd"
+require_relative "../resource"
class Chef
class Resource
class Launchd < Chef::Resource
- provides :launchd, os: "darwin"
+ unified_mode true
+ provides :launchd
- identity_attr :label
+ description "Use the **launchd** resource to manage system-wide services (daemons) and per-user services (agents) on the macOS platform."
+ introduced "12.8"
default_action :create
- allowed_actions :create, :create_if_missing, :delete, :enable, :disable
-
- def initialize(name, run_context = nil)
- super
- provider = Chef::Provider::Launchd
- resource_name = :launchd
- end
-
- property :label, String, default: lazy { name }, identity: true
- property :backup, [Integer, FalseClass]
- property :cookbook, String
- property :group, [String, Integer]
- property :hash, Hash
- property :mode, [String, Integer]
- property :owner, [String, Integer]
- property :path, String
- property :source, String
- property :session_type, String
-
- property :type, String, default: "daemon", coerce: proc { |type|
- type = type ? type.downcase : "daemon"
- types = %w{daemon agent}
-
- unless types.include?(type)
- error_msg = "type must be daemon or agent"
- raise Chef::Exceptions::ValidationFailed, error_msg
- end
- type
- }
+ allowed_actions :create, :create_if_missing, :delete, :enable, :disable, :restart
+
+ property :label, String,
+ name_property: true,
+ description: "The unique identifier for the job."
+
+ property :backup, [Integer, FalseClass],
+ desired_state: false,
+ description: "The number of backups to be kept in /var/chef/backup. Set to false to prevent backups from being kept."
+
+ property :cookbook, String,
+ desired_state: false,
+ description: "The name of the cookbook in which the source files are located."
+
+ property :group, [String, Integer],
+ description: "When launchd is run as the root user, the group to run the job as. If the username property is specified and this property is not, this value is set to the default group for the user."
+
+ property :plist_hash, Hash,
+ introduced: "12.19",
+ description: "A Hash of key value pairs used to create the launchd property list."
+
+ property :mode, [String, Integer],
+ description: "A quoted 3-5 character string that defines the octal mode. For example: '755', '0755', or 00755."
+
+ property :owner, [String, Integer],
+ description: "A string or ID that identifies the group owner by user name, including fully qualified user names such as domain_user or user@domain. If this value is not specified, existing owners remain unchanged and new owner assignments use the current user (when necessary)."
+
+ property :path, String,
+ description: "The path to the directory. Using a fully qualified path is recommended, but is not always required."
+
+ property :source, String,
+ description: "The path to the launchd property list."
+
+ property :session_type, String,
+ description: "The type of launchd plist to be created. Possible values: system (default) or user."
+
+ # StartCalendarInterval has some gotchas so we coerce it to help sanity
+ # check. According to `man 5 launchd.plist`:
+ # StartCalendarInterval <dictionary of integers or array of dictionaries of integers>
+ # ... Missing arguments are considered to be wildcard.
+ # What the man page doesn't state, but what was observed (OSX 10.11.5, launchctl v3.4.0)
+ # Is that keys that are specified, but invalid, will also be treated as a wildcard
+ # this means that an entry like:
+ # { "Hour"=>0, "Weekday"=>"6-7"}
+ # will not just run on midnight of Sat and Sun, rather it will run _every_ midnight.
+ property :start_calendar_interval, [Hash, Array],
+ description: "A Hash (similar to crontab) that defines the calendar frequency at which a job is started or an Array.",
+ coerce: proc { |type|
+ # Coerce into an array of hashes to make validation easier
+ array = if type.is_a?(Array)
+ type
+ else
+ [type]
+ end
+
+ # Check to make sure that our array only has hashes
+ unless array.all? { |obj| obj.is_a?(Hash) }
+ error_msg = "start_calendar_interval must be a single hash or an array of hashes!"
+ raise Chef::Exceptions::ValidationFailed, error_msg
+ end
+
+ # Make sure the hashes don't have any incorrect keys/values
+ array.each do |entry|
+ allowed_keys = %w{Minute Hour Day Weekday Month}
+ unless entry.keys.all? { |key| allowed_keys.include?(key) }
+ failed_keys = entry.keys.reject { |k| allowed_keys.include?(k) }.join(", ")
+ error_msg = "The following key(s): #{failed_keys} are invalid for start_calendar_interval, must be one of: #{allowed_keys.join(", ")}"
+ raise Chef::Exceptions::ValidationFailed, error_msg
+ end
+
+ unless entry.values.all? { |val| val.is_a?(Integer) }
+ failed_values = entry.values.reject { |val| val.is_a?(Integer) }.join(", ")
+ error_msg = "Invalid value(s) (#{failed_values}) for start_calendar_interval item. Values must be integers!"
+ raise Chef::Exceptions::ValidationFailed, error_msg
+ end
+ end
+
+ # Don't return array if we only have one entry
+ if array.size == 1
+ array.first
+ else
+ array
+ end
+ }
+
+ property :type, String,
+ description: "The type of resource. Possible values: daemon (default), agent.",
+ default: "daemon", coerce: proc { |type|
+ type = type ? type.downcase : "daemon"
+ types = %w{daemon agent}
+
+ unless types.include?(type)
+ error_msg = "type must be daemon or agent"
+ raise Chef::Exceptions::ValidationFailed, error_msg
+ end
+ type
+ }
# Apple LaunchD Keys
- property :abandon_process_group, [ TrueClass, FalseClass ]
- property :debug, [ TrueClass, FalseClass ]
- property :disabled, [ TrueClass, FalseClass ], default: false
- property :enable_globbing, [ TrueClass, FalseClass ]
- property :enable_transactions, [ TrueClass, FalseClass ]
- property :environment_variables, Hash
- property :exit_timeout, Integer
- property :hard_resource_limits, Hash
- property :inetd_compatibility, Hash
- property :init_groups, [ TrueClass, FalseClass ]
- property :keep_alive, [ TrueClass, FalseClass, Hash ]
- property :launch_only_once, [ TrueClass, FalseClass ]
- property :ld_group, String
- property :limit_load_from_hosts, Array
- property :limit_load_to_hosts, Array
- property :limit_load_to_session_type, String
- property :low_priority_io, [ TrueClass, FalseClass ]
- property :mach_services, Hash
- property :nice, Integer
- property :on_demand, [ TrueClass, FalseClass ]
- property :process_type, String
- property :program, String
- property :program_arguments, Array
- property :queue_directories, Array
- property :root_directory, String
- property :run_at_load, [ TrueClass, FalseClass ]
- property :sockets, Hash
- property :soft_resource_limits, Array
- property :standard_error_path, String
- property :standard_in_path, String
- property :standard_out_path, String
- property :start_calendar_interval, Hash
- property :start_interval, Integer
- property :start_on_mount, [ TrueClass, FalseClass ]
- property :throttle_interval, Integer
- property :time_out, Integer
- property :umask, Integer
- property :username, String
- property :wait_for_debugger, [ TrueClass, FalseClass ]
- property :watch_paths, Array
- property :working_directory, String
+ property :abandon_process_group, [ TrueClass, FalseClass ],
+ description: "If a job dies, all remaining processes with the same process ID may be kept running. Set to true to kill all remaining processes."
+
+ property :debug, [ TrueClass, FalseClass ],
+ description: "Sets the log mask to `LOG_DEBUG` for this job."
+
+ property :disabled, [ TrueClass, FalseClass ], default: false,
+ description: "Hints to `launchctl` to not submit this job to launchd."
+
+ property :enable_globbing, [ TrueClass, FalseClass ],
+ description: "Update program arguments before invocation."
+
+ property :enable_transactions, [ TrueClass, FalseClass ],
+ description: "Track in-progress transactions; if none, then send the `SIGKILL` signal."
+
+ property :environment_variables, Hash,
+ description: "Additional environment variables to set before running a job."
+
+ property :exit_timeout, Integer,
+ description: "The amount of time (in seconds) launchd waits before sending a `SIGKILL` signal."
+
+ property :hard_resource_limits, Hash,
+ description: "A Hash of resource limits to be imposed on a job."
+
+ property :inetd_compatibility, Hash,
+ description: "Specifies if a daemon expects to be run as if it were launched from inetd. Set to `wait => true` to pass standard input, output, and error file descriptors. Set to `wait => false` to call the accept system call on behalf of the job, and then pass standard input, output, and error file descriptors."
+
+ property :init_groups, [ TrueClass, FalseClass ],
+ description: "Specify if `initgroups` is called before running a job."
+
+ property :keep_alive, [ TrueClass, FalseClass, Hash ],
+ introduced: "12.14",
+ description: "Keep a job running continuously (true) or allow demand and conditions on the node to determine if the job keeps running (`false`)."
+
+ property :launch_events, [ Hash ],
+ introduced: "15.1",
+ description: "Specify higher-level event types to be used as launch-on-demand event sources."
+
+ property :launch_only_once, [ TrueClass, FalseClass ],
+ description: "Specify if a job can be run only one time. Set this value to true if a job cannot be restarted without a full machine reboot."
+
+ property :ld_group, String,
+ description: "The group name."
+
+ property :limit_load_from_hosts, Array,
+ description: "An array of hosts to which this configuration file does not apply, i.e. 'apply this configuration file to all hosts not specified in this array'."
+
+ property :limit_load_to_hosts, Array,
+ description: "An array of hosts to which this configuration file applies."
+
+ property :limit_load_to_session_type, [ Array, String ],
+ description: "The session type(s) to which this configuration file applies."
+
+ property :low_priority_io, [ TrueClass, FalseClass ],
+ description: "Specify if the kernel on the node should consider this daemon to be low priority during file system I/O."
+
+ property :mach_services, Hash,
+ description: "Specify services to be registered with the bootstrap subsystem."
+
+ property :nice, Integer,
+ description: "The program scheduling priority value in the range -20 to 19.",
+ callbacks: { "should be a Integer between -20 and 19" => proc { |v| v >= -20 && v <= 19 } }
+
+ property :on_demand, [ TrueClass, FalseClass ],
+ description: "Keep a job alive. Only applies to macOS version 10.4 (and earlier); use `keep_alive` instead for newer versions."
+
+ property :process_type, String,
+ description: "The intended purpose of the job: `Adaptive`, `Background`, `Interactive`, or `Standard`."
+
+ property :program, String,
+ description: "The first argument of execvp, typically the file name associated with the file to be executed. This value must be specified if program_arguments is not specified, and vice-versa."
+
+ property :program_arguments, Array,
+ description: "The second argument of execvp. If program is not specified, this property must be specified and will be handled as if it were the first argument."
+
+ property :queue_directories, Array,
+ description: "An array of non-empty directories which, if any are modified, will cause a job to be started."
+
+ property :root_directory, String,
+ description: "`chroot` to this directory, and then run the job."
+
+ property :run_at_load, [ TrueClass, FalseClass ],
+ description: "Launch a job once (at the time it is loaded)."
+
+ property :sockets, Hash,
+ description: "A Hash of on-demand sockets that notify launchd when a job should be run."
+
+ property :soft_resource_limits, Array,
+ description: "A Hash of resource limits to be imposed on a job."
+
+ property :standard_error_path, String,
+ description: "The file to which standard error (`stderr`) is sent."
+
+ property :standard_in_path, String,
+ description: "The file to which standard input (`stdin`) is sent."
+
+ property :standard_out_path, String,
+ description: "The file to which standard output (`stdout`) is sent."
+
+ property :start_interval, Integer,
+ description: "The frequency (in seconds) at which a job is started."
+
+ property :start_on_mount, [ TrueClass, FalseClass ],
+ description: "Start a job every time a file system is mounted."
+
+ property :throttle_interval, Integer,
+ description: "The frequency (in seconds) at which jobs are allowed to spawn."
+
+ property :time_out, Integer,
+ description: "The amount of time (in seconds) a job may be idle before it times out. If no value is specified, the default timeout value for launchd will be used."
+
+ property :umask, Integer,
+ description: "A decimal value to pass to `umask` before running a job."
+
+ property :username, String,
+ description: "When launchd is run as the root user, the user to run the job as."
+
+ property :wait_for_debugger, [ TrueClass, FalseClass ],
+ description: "Specify if launchd has a job wait for a debugger to attach before executing code."
+
+ property :watch_paths, Array,
+ description: "An array of paths which, if any are modified, will cause a job to be started."
+
+ property :working_directory, String,
+ description: "`chdir` to this directory, and then run the job."
end
end
end
diff --git a/lib/chef/resource/link.rb b/lib/chef/resource/link.rb
index 5717ec7bad..e1422d0d61 100644
--- a/lib/chef/resource/link.rb
+++ b/lib/chef/resource/link.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,91 +17,56 @@
# limitations under the License.
#
-require "chef/resource"
-require "chef/mixin/securable"
+require_relative "../resource"
+require_relative "../mixin/securable"
class Chef
class Resource
class Link < Chef::Resource
include Chef::Mixin::Securable
+ unified_mode true
- identity_attr :target_file
+ provides :link
- state_attrs :to, :owner, :group
+ description "Use the **link** resource to create symbolic or hard links.\n\n"\
+ "A symbolic link—sometimes referred to as a soft link—is a directory entry"\
+ " that associates a file name with a string that contains an absolute or"\
+ " relative path to a file on any file system. In other words, 'a file that"\
+ " contains a path that points to another file.' A symbolic link creates a new"\
+ " file with a new inode that points to the inode location of the original file.\n\n"\
+ "A hard link is a directory entry that associates a file with another file in the"\
+ " same file system. In other words, 'multiple directory entries to the same file.'"\
+ " A hard link creates a new file that points to the same inode as the original file."
+
+ state_attrs :owner # required since it's not a property below
default_action :create
allowed_actions :create, :delete
- def initialize(name, run_context = nil)
- verify_links_supported!
- super
- @to = nil
- @link_type = :symbolic
- @target_file = name
- end
-
- def to(arg = nil)
- set_or_return(
- :to,
- arg,
- :kind_of => String
- )
- end
+ property :target_file, String,
+ description: "An optional property to set the target file if it differs from the resource block's name.",
+ name_property: true
- def target_file(arg = nil)
- set_or_return(
- :target_file,
- arg,
- :kind_of => String
- )
- end
+ property :to, String,
+ description: "The actual file to which the link is to be created."
- def link_type(arg = nil)
- real_arg = arg.kind_of?(String) ? arg.to_sym : arg
- set_or_return(
- :link_type,
- real_arg,
- :equal_to => [ :symbolic, :hard ]
- )
- end
+ property :link_type, [String, Symbol],
+ description: "The type of link: :symbolic or :hard.",
+ coerce: proc { |arg| arg.is_a?(String) ? arg.to_sym : arg },
+ equal_to: %i{symbolic hard}, default: :symbolic
- def group(arg = nil)
- set_or_return(
- :group,
- arg,
- :regex => Chef::Config[:group_valid_regex]
- )
- end
+ property :group, [String, Integer],
+ description: "A group name or ID number that identifies the group associated with a symbolic link.",
+ regex: [Chef::Config[:group_valid_regex]]
- def owner(arg = nil)
- set_or_return(
- :owner,
- arg,
- :regex => Chef::Config[:user_valid_regex]
- )
- end
+ property :owner, [String, Integer],
+ description: "The owner associated with a symbolic link.",
+ regex: [Chef::Config[:user_valid_regex]]
# make link quack like a file (XXX: not for public consumption)
def path
target_file
end
-
- private
-
- def verify_links_supported!
- # On certain versions of windows links are not supported. Make
- # sure we are not on such a platform.
-
- if Chef::Platform.windows?
- require "chef/win32/file"
- begin
- Chef::ReservedNames::Win32::File.verify_links_supported!
- rescue Chef::Exceptions::Win32APIFunctionNotImplemented => e
- Chef::Log.fatal("Link resource is not supported on this version of Windows")
- raise e
- end
- end
- end
end
end
end
diff --git a/lib/chef/resource/locale.rb b/lib/chef/resource/locale.rb
new file mode 100644
index 0000000000..fafa1a5caa
--- /dev/null
+++ b/lib/chef/resource/locale.rb
@@ -0,0 +1,184 @@
+#
+# Copyright:: 2011-2016, Heavy Water Software Inc.
+# Copyright:: Copyright (c) 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_relative "../resource"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Resource
+ class Locale < Chef::Resource
+ unified_mode true
+ provides :locale
+
+ description "Use the **locale** resource to set the system's locale on Debian and Windows systems. Windows support was added in Chef Infra Client 16.0"
+ introduced "14.5"
+
+ examples <<~DOC
+ Set the lang to 'en_US.UTF-8'
+
+ ```ruby
+ locale 'set system locale' do
+ lang 'en_US.UTF-8'
+ end
+ ```
+ DOC
+
+ LC_VARIABLES ||= %w{LC_ADDRESS LC_COLLATE LC_CTYPE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE LC_TIME}.freeze
+ LOCALE_CONF ||= "/etc/locale.conf".freeze
+ LOCALE_REGEX ||= /\A\S+/.freeze
+ LOCALE_PLATFORM_FAMILIES ||= %w{debian windows}.freeze
+
+ property :lang, String,
+ description: "Sets the default system language.",
+ regex: [LOCALE_REGEX],
+ validation_message: "The provided lang is not valid. It should be a non-empty string without any leading whitespace."
+
+ property :lc_env, Hash,
+ description: "A Hash of LC_* env variables in the form of `({ 'LC_ENV_VARIABLE' => 'VALUE' })`.",
+ default: lazy { {} },
+ coerce: proc { |h|
+ if h.respond_to?(:keys)
+ invalid_keys = h.keys - LC_VARIABLES
+ unless invalid_keys.empty?
+ error_msg = "Key of option lc_env must be equal to one of: \"#{LC_VARIABLES.join('", "')}\"! You passed \"#{invalid_keys.join(", ")}\"."
+ raise Chef::Exceptions::ValidationFailed, error_msg
+ end
+ end
+ unless h.values.all? { |x| x =~ LOCALE_REGEX }
+ error_msg = "Values of option lc_env should be non-empty string without any leading whitespace."
+ raise Chef::Exceptions::ValidationFailed, error_msg
+ end
+ h
+ }
+
+ # @deprecated Use {#lc_env} instead of this property.
+ # {#lc_env} uses Hash with specific LC var as key.
+ # @raise [Chef::Deprecated]
+ #
+ def lc_all(arg = nil)
+ unless arg.nil?
+ Chef.deprecated(:locale_lc_all, "Changing LC_ALL can break #{ChefUtils::Dist::Infra::PRODUCT}'s parsing of command output in unexpected ways.\n Use one of the more specific LC_ properties as needed.")
+ end
+ end
+
+ load_current_value do
+ if windows?
+ lang get_system_locale_windows
+ else
+ begin
+ old_content = ::File.read(LOCALE_CONF)
+ locale_values = Hash[old_content.split("\n").map { |v| v.split("=") }]
+ lang locale_values["LANG"]
+ rescue Errno::ENOENT => e
+ false
+ end
+ end
+ end
+
+ # Gets the System-locale setting for the current computer.
+ # @see https://docs.microsoft.com/en-us/powershell/module/international/get-winsystemlocale
+ # @return [String] the current value of the System-locale setting.
+ #
+ def get_system_locale_windows
+ powershell_exec("Get-WinSystemLocale").result["Name"]
+ end
+
+ action :update do
+ description "Update the system's locale."
+ converge_if_changed do
+ set_system_locale
+ end
+ end
+
+ action_class do
+ # Avoid running this resource on platforms that don't use /etc/locale.conf
+ #
+ def define_resource_requirements
+ requirements.assert(:all_actions) do |a|
+ a.assertion { LOCALE_PLATFORM_FAMILIES.include?(node[:platform_family]) }
+ a.failure_message(Chef::Exceptions::ProviderNotFound, "The locale resource is not supported on platform family: #{node[:platform_family]}")
+ end
+
+ requirements.assert(:all_actions) do |a|
+ # RHEL/CentOS type platforms don't have locale-gen
+ a.assertion { shell_out("locale-gen") }
+ a.failure_message(Chef::Exceptions::ProviderNotFound, "The locale resource requires the locale-gen tool")
+ end
+ end
+
+ # Generates the localization files from templates using locale-gen.
+ # @see http://manpages.ubuntu.com/manpages/cosmic/man8/locale-gen.8.html
+ # @raise [Mixlib::ShellOut::ShellCommandFailed] not a supported language or locale
+ #
+ def generate_locales
+ shell_out!("locale-gen #{unavailable_locales.join(" ")}", timeout: 1800)
+ end
+
+ # Sets the system locale for the current computer.
+ #
+ def set_system_locale
+ if windows?
+ # Sets the system locale for the current computer.
+ # @see https://docs.microsoft.com/en-us/powershell/module/internationalcmdlets/set-winsystemlocale
+ #
+ response = powershell_exec("Set-WinSystemLocale -SystemLocale #{new_resource.lang}")
+ raise response.errors.join(" ") if response.error?
+ else
+ generate_locales unless unavailable_locales.empty?
+ update_locale
+ end
+ end
+
+ # Updates system locale by appropriately writing them in /etc/locale.conf
+ # @note This locale change won't affect the current run. At this time it is an exercise
+ # left to the user to restart or reboot if the locale change is required at
+ # later part of the client run.
+ # @see https://wiki.archlinux.org/index.php/locale#Setting_the_system_locale
+ #
+ def update_locale
+ file "Updating system locale" do
+ path LOCALE_CONF
+ content new_content
+ end
+ end
+
+ # @return [Array<String>] Locales that user wants to set but are not available on
+ # the system. They are required to be generated.
+ #
+ def unavailable_locales
+ @unavailable_locales ||= begin
+ available = shell_out!("locale -a").stdout.split("\n")
+ required = [new_resource.lang, new_resource.lc_env.values].flatten.compact.uniq
+ required - available
+ end
+ end
+
+ # @return [String] Contents that are required to be
+ # updated in /etc/locale.conf
+ #
+ def new_content
+ @new_content ||= begin
+ content = {}
+ content = new_resource.lc_env.dup if new_resource.lc_env
+ content["LANG"] = new_resource.lang if new_resource.lang
+ content.sort.map { |t| t.join("=") }.join("\n") + "\n"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/log.rb b/lib/chef/resource/log.rb
index 8f7879872f..e9f8bc315a 100644
--- a/lib/chef/resource/log.rb
+++ b/lib/chef/resource/log.rb
@@ -1,7 +1,7 @@
#
# Author:: Cary Penniman (<cary@rightscale.com>)
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,60 +17,50 @@
# limitations under the License.
#
-require "chef/resource"
-require "chef/provider/log"
+require_relative "../resource"
class Chef
class Resource
+ # @example logging at default info level
+ # log "your string to log"
+ #
+ # @example logging at specified debug level
+ # log "a debug string" do
+ # level :debug
+ # end
class Log < Chef::Resource
+ unified_mode true
- identity_attr :message
+ provides :log, target_mode: true
- default_action :write
+ description "Use the **log** resource to create log entries. The log resource behaves"\
+ " like any other resource: built into the resource collection during the"\
+ " compile phase, and then run during the execution phase. (To create a log"\
+ " entry that is not built into the resource collection, use Chef::Log instead"\
+ " of the log resource.)"
- # Sends a string from a recipe to a log provider
- #
- # log "some string to log" do
- # level :info # (default) also supports :warn, :debug, and :error
- # end
- #
- # === Example
- # log "your string to log"
- #
- # or
- #
- # log "a debug string" { level :debug }
- #
+ property :message, String,
+ name_property: true, identity: true,
+ description: "The message to be added to a log file. If not specified we'll use the resource's name instead."
- # Initialize log resource with a name as the string to log
- #
- # === Parameters
- # name<String>:: Message to log
- # collection<Array>:: Collection of included recipes
- # node<Chef::Node>:: Node where resource will be used
- def initialize(name, run_context = nil)
- super
- @level = :info
- @message = name
- end
+ property :level, Symbol,
+ equal_to: %i{debug info warn error fatal}, default: :info,
+ description: "The logging level to display this message at."
- def message(arg = nil)
- set_or_return(
- :message,
- arg,
- :kind_of => String
- )
- end
+ allowed_actions :write
+ default_action :write
- # <Symbol> Log level, one of :debug, :info, :warn, :error or :fatal
- def level(arg = nil)
- set_or_return(
- :level,
- arg,
- :equal_to => [ :debug, :info, :warn, :error, :fatal ]
- )
+ def suppress_up_to_date_messages?
+ true
end
+ # Write the log to Chef's log
+ #
+ # @return [true] Always returns true
+ action :write do
+ logger.send(new_resource.level, new_resource.message)
+ new_resource.updated_by_last_action(true) if Chef::Config[:count_log_resource_updates]
+ end
end
end
end
diff --git a/lib/chef/resource/lwrp_base.rb b/lib/chef/resource/lwrp_base.rb
index 7dfe147341..2cf891d530 100644
--- a/lib/chef/resource/lwrp_base.rb
+++ b/lib/chef/resource/lwrp_base.rb
@@ -2,7 +2,7 @@
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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/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
+require_relative "../resource"
+require_relative "../resource_resolver"
+require_relative "../node"
+require_relative "../log"
+require_relative "../exceptions"
+require_relative "../mixin/convert_to_class_name"
+require_relative "../mixin/from_file"
+require_relative "../mixin/params_validate" # for DelayedEvaluator
class Chef
class Resource
@@ -41,19 +41,15 @@ class Chef
include Chef::Mixin::ConvertToClassName
include Chef::Mixin::FromFile
- attr_accessor :loaded_lwrps
-
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.")
+ Chef::Log.trace("Custom resource #{filename} from cookbook #{cookbook_name} has already been loaded! Skipping the reload.")
return loaded_lwrps[filename]
end
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)
@@ -65,17 +61,17 @@ class Chef
define_singleton_method(:inspect) { to_s }
end
- Chef::Log.debug("Loaded contents of #{filename} into resource #{resource_name} (#{resource_class})")
+ Chef::Log.trace("Loaded contents of #{filename} into resource #{resource_name} (#{resource_class})")
+
+ # wire up the default resource name after the class is parsed only if we haven't declared one.
+ # (this ordering is important for MapCollision deprecation warnings)
+ resource_class.provides resource_name.to_sym unless Chef::ResourceResolver.includes_handler?(resource_name.to_sym, self)
- LWRPBase.loaded_lwrps[filename] = true
+ LWRPBase.loaded_lwrps[filename] = resource_class
- # Create the deprecated Chef::Resource::LwrpFoo class
- Chef::Resource.register_deprecated_lwrp_class(resource_class, convert_to_class_name(resource_name))
resource_class
end
- alias :attribute :property
-
# Adds +action_names+ to the list of valid actions for this resource.
# Does not include superclass's action list when appending.
def actions(*action_names)
@@ -90,7 +86,7 @@ class Chef
# @deprecated
def valid_actions(*args)
- Chef::Log.warn("`valid_actions' is deprecated, please use allowed_actions `instead'!")
+ Chef::Log.warn("`valid_actions` is deprecated, please use `allowed_actions` instead!")
allowed_actions(*args)
end
@@ -104,6 +100,8 @@ class Chef
protected
+ attr_writer :loaded_lwrps
+
def loaded_lwrps
@loaded_lwrps ||= {}
end
@@ -116,6 +114,7 @@ class Chef
# +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
end
diff --git a/lib/chef/resource/macos_userdefaults.rb b/lib/chef/resource/macos_userdefaults.rb
new file mode 100644
index 0000000000..a150aeb9ed
--- /dev/null
+++ b/lib/chef/resource/macos_userdefaults.rb
@@ -0,0 +1,256 @@
+#
+# Copyright:: 2011-2018, Joshua Timberman
+# Copyright:: Copyright (c) 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_relative "../resource"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+autoload :Plist, "plist"
+
+class Chef
+ class Resource
+ class MacosUserDefaults < Chef::Resource
+ unified_mode true
+
+ # align with apple's marketing department
+ provides(:macos_userdefaults) { true }
+ provides(:mac_os_x_userdefaults) { true }
+
+ description "Use the **macos_userdefaults** resource to manage the macOS user defaults system. The properties of this resource are passed to the defaults command, and the parameters follow the convention of that command. See the defaults(1) man page for details on how the tool works."
+ introduced "14.0"
+ examples <<~DOC
+ **Specify a global domain value**
+
+ ```ruby
+ macos_userdefaults 'Full keyboard access to all controls' do
+ key 'AppleKeyboardUIMode'
+ value 2
+ end
+ ```
+
+ **Setting a value on a specific domain**
+
+ ```ruby
+ macos_userdefaults 'Enable macOS firewall' do
+ domain '/Library/Preferences/com.apple.alf'
+ key 'globalstate'
+ value 1
+ end
+ ```
+
+ **Specifying the type of a key to skip automatic type detection**
+
+ ```ruby
+ macos_userdefaults 'Finder expanded save dialogs' do
+ key 'NSNavPanelExpandedStateForSaveMode'
+ value 'TRUE'
+ type 'bool'
+ end
+ ```
+ DOC
+
+ property :domain, String,
+ description: "The domain that the user defaults belong to.",
+ default: "NSGlobalDomain",
+ default_description: "NSGlobalDomain: the global domain.",
+ desired_state: false
+
+ property :global, [TrueClass, FalseClass],
+ description: "Determines whether or not the domain is global.",
+ deprecated: true,
+ default: false,
+ desired_state: false
+
+ property :key, String,
+ description: "The preference key.",
+ required: true
+
+ property :host, [String, Symbol],
+ description: "Set either :current or a hostname to set the user default at the host level.",
+ desired_state: false,
+ introduced: "16.3"
+
+ property :value, [Integer, Float, String, TrueClass, FalseClass, Hash, Array],
+ description: "The value of the key. Note: With the `type` property set to `bool`, `String` forms of Boolean true/false values that Apple accepts in the defaults command will be coerced: 0/1, 'TRUE'/'FALSE,' 'true'/false', 'YES'/'NO', or 'yes'/'no'.",
+ required: [:write],
+ coerce: proc { |v| v.is_a?(Hash) ? v.transform_keys(&:to_s) : v } # make sure keys are all strings for comparison
+
+ property :type, String,
+ description: "The value type of the preference key.",
+ equal_to: %w{bool string int float array dict},
+ desired_state: false
+
+ property :user, String,
+ description: "The system user that the default will be applied to.",
+ desired_state: false
+
+ property :sudo, [TrueClass, FalseClass],
+ description: "Set to true if the setting you wish to modify requires privileged access. This requires passwordless sudo for the '/usr/bin/defaults' command to be setup for the user running #{ChefUtils::Dist::Infra::PRODUCT}.",
+ default: false,
+ desired_state: false
+
+ load_current_value do |desired|
+ Chef::Log.debug "#load_current_value: shelling out \"#{defaults_export_cmd(desired).join(" ")}\" to determine state"
+ state = shell_out(defaults_export_cmd(desired), user: desired.user)
+
+ if state.error? || state.stdout.empty?
+ Chef::Log.debug "#load_current_value: #{defaults_export_cmd(desired).join(" ")} returned stdout: #{state.stdout} and stderr: #{state.stderr}"
+ current_value_does_not_exist!
+ end
+
+ plist_data = ::Plist.parse_xml(state.stdout)
+
+ # handle the situation where the key doesn't exist in the domain
+ if plist_data.key?(desired.key)
+ key desired.key
+ else
+ current_value_does_not_exist!
+ end
+
+ value plist_data[desired.key]
+ end
+
+ #
+ # The defaults command to export a domain
+ #
+ # @return [Array] defaults command
+ #
+ def defaults_export_cmd(resource)
+ state_cmd = ["/usr/bin/defaults"]
+
+ if resource.host == "current"
+ state_cmd.concat(["-currentHost"])
+ elsif resource.host # they specified a non-nil value, which is a hostname
+ state_cmd.concat(["-host", resource.host])
+ end
+
+ state_cmd.concat(["export", resource.domain, "-"])
+ state_cmd
+ end
+
+ action :write do
+ description "Write the value to the specified domain/key."
+
+ converge_if_changed do
+ cmd = defaults_modify_cmd
+ Chef::Log.debug("Updating defaults value by shelling out: #{cmd.join(" ")}")
+
+ shell_out!(cmd, user: new_resource.user)
+ end
+ end
+
+ action :delete do
+ description "Delete a key from a domain."
+
+ # if it's not there there's nothing to remove
+ return unless current_resource
+
+ converge_by("delete domain:#{new_resource.domain} key:#{new_resource.key}") do
+
+ cmd = defaults_modify_cmd
+ Chef::Log.debug("Removing defaults key by shelling out: #{cmd.join(" ")}")
+
+ shell_out!(cmd, user: new_resource.user)
+ end
+ end
+
+ action_class do
+ #
+ # The command used to write or delete delete values from domains
+ #
+ # @return [Array] Array representation of defaults command to run
+ #
+ def defaults_modify_cmd
+ cmd = ["/usr/bin/defaults"]
+
+ if new_resource.host == :current
+ cmd.concat(["-currentHost"])
+ elsif new_resource.host # they specified a non-nil value, which is a hostname
+ cmd.concat(["-host", new_resource.host])
+ end
+
+ cmd.concat([action.to_s, new_resource.domain, new_resource.key])
+ cmd.concat(processed_value) if action == :write
+ cmd.prepend("sudo") if new_resource.sudo
+ cmd
+ end
+
+ #
+ # convert the provided value into the format defaults expects
+ #
+ # @return [array] array of values starting with the type if applicable
+ #
+ def processed_value
+ type = new_resource.type || value_type(new_resource.value)
+
+ # when dict this creates an array of values ["Key1", "Value1", "Key2", "Value2" ...]
+ cmd_values = ["-#{type}"]
+
+ case type
+ when "dict"
+ cmd_values.concat(new_resource.value.flatten)
+ when "array"
+ cmd_values.concat(new_resource.value)
+ when "bool"
+ cmd_values.concat(bool_to_defaults_bool(new_resource.value))
+ else
+ cmd_values.concat([new_resource.value])
+ end
+
+ cmd_values
+ end
+
+ #
+ # defaults booleans on the CLI must be 'TRUE' or 'FALSE' so convert various inputs to that
+ #
+ # @param [String, Integer, Boolean] input <description>
+ #
+ # @return [String] TRUE or FALSE
+ #
+ def bool_to_defaults_bool(input)
+ return ["TRUE"] if [true, "TRUE", "1", "true", "YES", "yes"].include?(input)
+ return ["FALSE"] if [false, "FALSE", "0", "false", "NO", "no"].include?(input)
+
+ # make sure it's very clear bad input was given
+ raise ArgumentError, "#{input} cannot be converted to a boolean value for use with Apple's defaults command. Acceptable values are: 'TRUE', 'YES', 'true, 'yes', '0', true, 'FALSE', 'false', 'NO', 'no', '1', or false."
+ end
+
+ #
+ # convert ruby type to defaults type
+ #
+ # @param [Integer, Float, String, TrueClass, FalseClass, Hash, Array] value The value being set
+ #
+ # @return [string, nil] the type value used by defaults or nil if not applicable
+ #
+ def value_type(value)
+ case value
+ when true, false
+ "bool"
+ when Integer
+ "int"
+ when Float
+ "float"
+ when Hash
+ "dict"
+ when Array
+ "array"
+ when String
+ "string"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/macosx_service.rb b/lib/chef/resource/macosx_service.rb
index 08c748bead..693b8a4b87 100644
--- a/lib/chef/resource/macosx_service.rb
+++ b/lib/chef/resource/macosx_service.rb
@@ -16,42 +16,22 @@
# limitations under the License.
#
-require "chef/resource/service"
+require_relative "service"
class Chef
class Resource
class MacosxService < Chef::Resource::Service
+ unified_mode true
- provides :macosx_service, os: "darwin"
+ provides :macosx_service
provides :service, os: "darwin"
- identity_attr :service_name
+ description "Use the **macosx_service** resource to manage services on the macOS platform."
- state_attrs :enabled, :running
+ property :plist, String,
+ description: "A plist to use in the case where the filename and label for the service do not match."
- 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
+ property :session_type, String
end
end
diff --git a/lib/chef/resource/macports_package.rb b/lib/chef/resource/macports_package.rb
index 3685334c17..70fa84dec7 100644
--- a/lib/chef/resource/macports_package.rb
+++ b/lib/chef/resource/macports_package.rb
@@ -1,6 +1,6 @@
#
# Author:: David Balatero (<dbalatero@gmail.com>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,12 +16,22 @@
# limitations under the License.
#
-require "chef/resource/package"
+require_relative "package"
class Chef
class Resource
class MacportsPackage < Chef::Resource::Package
- resource_name :macports_package
+ unified_mode true
+ provides :macports_package
+
+ description "Use the **macports_package** resource to manage packages for the macOS platform using the MacPorts package management system."
+
+ property :package_name, String,
+ description: "An optional property to set the package name if it differs from the resource block's name.",
+ identity: true
+
+ property :version, String,
+ description: "The version of a package to be installed or upgraded."
end
end
end
diff --git a/lib/chef/resource/mdadm.rb b/lib/chef/resource/mdadm.rb
index df6e705f15..f2e610c1cf 100644
--- a/lib/chef/resource/mdadm.rb
+++ b/lib/chef/resource/mdadm.rb
@@ -17,94 +17,109 @@
# limitations under the License.
#
-require "chef/resource"
+require_relative "../resource"
class Chef
class Resource
class Mdadm < Chef::Resource
+ unified_mode true
- identity_attr :raid_device
+ provides :mdadm
- state_attrs :devices, :level, :chunk
+ description "Use the **mdadm** resource to manage RAID devices in a Linux environment using the mdadm utility. The mdadm resource"\
+ " will create and assemble an array, but it will not create the config file that is used to persist the array upon"\
+ " reboot. If the config file is required, it must be done by specifying a template with the correct array layout,"\
+ " and then by using the mount provider to create a file systems table (fstab) entry."
default_action :create
allowed_actions :create, :assemble, :stop
- def initialize(name, run_context = nil)
- super
-
- @chunk = 16
- @devices = []
- @exists = false
- @level = 1
- @metadata = "0.90"
- @bitmap = nil
- @raid_device = name
- @layout = nil
- end
+ property :chunk, Integer,
+ default: 16,
+ description: "The chunk size. This property should not be used for a RAID 1 mirrored pair (i.e. when the `level` property is set to `1`)."
- def chunk(arg = nil)
- set_or_return(
- :chunk,
- arg,
- :kind_of => [ Integer ]
- )
- end
+ property :devices, Array,
+ default: lazy { [] },
+ description: "The devices to be part of a RAID array."
- def devices(arg = nil)
- set_or_return(
- :devices,
- arg,
- :kind_of => [ Array ]
- )
- end
+ # @todo this should get refactored away
+ property :exists, [ TrueClass, FalseClass ],
+ default: false,
+ skip_docs: true
- def exists(arg = nil)
- set_or_return(
- :exists,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
+ property :level, Integer,
+ default: 1,
+ description: "The RAID level."
- def level(arg = nil)
- set_or_return(
- :level,
- arg,
- :kind_of => [ Integer ]
- )
- end
+ property :metadata, String,
+ default: "0.90",
+ description: "The superblock type for RAID metadata."
+
+ property :bitmap, String,
+ description: "The path to a file in which a write-intent bitmap is stored."
+
+ property :raid_device, String,
+ name_property: true,
+ description: "An optional property to specify the name of the RAID device if it differs from the resource block's name."
+
+ property :layout, String,
+ description: "The RAID5 parity algorithm. Possible values: `left-asymmetric` (or `la`), `left-symmetric` (or ls), `right-asymmetric` (or `ra`), or `right-symmetric` (or `rs`)."
+
+ action_class do
+ def load_current_resource
+ @current_resource = Chef::Resource::Mdadm.new(new_resource.name)
+ current_resource.raid_device(new_resource.raid_device)
+ logger.trace("#{new_resource} checking for software raid device #{current_resource.raid_device}")
- def metadata(arg = nil)
- set_or_return(
- :metadata,
- arg,
- :kind_of => [ String ]
- )
+ device_not_found = 4
+ mdadm = shell_out!("mdadm", "--detail", "--test", new_resource.raid_device, returns: [0, device_not_found])
+ exists = (mdadm.status == 0)
+ current_resource.exists(exists)
+ end
end
- def bitmap(arg = nil)
- set_or_return(
- :bitmap,
- arg,
- :kind_of => [ String ]
- )
+ action :create do
+ unless current_resource.exists
+ converge_by("create RAID device #{new_resource.raid_device}") do
+ command = "yes | mdadm --create #{new_resource.raid_device} --level #{new_resource.level}"
+ command << " --chunk=#{new_resource.chunk}" unless new_resource.level == 1
+ command << " --metadata=#{new_resource.metadata}"
+ command << " --bitmap=#{new_resource.bitmap}" if new_resource.bitmap
+ command << " --layout=#{new_resource.layout}" if new_resource.layout
+ command << " --raid-devices #{new_resource.devices.length} #{new_resource.devices.join(" ")}"
+ logger.trace("#{new_resource} mdadm command: #{command}")
+ shell_out!(command)
+ logger.info("#{new_resource} created raid device (#{new_resource.raid_device})")
+ end
+ else
+ logger.trace("#{new_resource} raid device already exists, skipping create (#{new_resource.raid_device})")
+ end
end
- def raid_device(arg = nil)
- set_or_return(
- :raid_device,
- arg,
- :kind_of => [ String ]
- )
+ action :assemble do
+ unless current_resource.exists
+ converge_by("assemble RAID device #{new_resource.raid_device}") do
+ command = "yes | mdadm --assemble #{new_resource.raid_device} #{new_resource.devices.join(" ")}"
+ logger.trace("#{new_resource} mdadm command: #{command}")
+ shell_out!(command)
+ logger.info("#{new_resource} assembled raid device (#{new_resource.raid_device})")
+ end
+ else
+ logger.trace("#{new_resource} raid device already exists, skipping assemble (#{new_resource.raid_device})")
+ end
end
- def layout(arg = nil)
- set_or_return(
- :layout,
- arg,
- :kind_of => [ String ]
- )
+ action :stop do
+ if current_resource.exists
+ converge_by("stop RAID device #{new_resource.raid_device}") do
+ command = "yes | mdadm --stop #{new_resource.raid_device}"
+ logger.trace("#{new_resource} mdadm command: #{command}")
+ shell_out!(command)
+ logger.info("#{new_resource} stopped raid device (#{new_resource.raid_device})")
+ end
+ else
+ logger.trace("#{new_resource} raid device doesn't exist (#{new_resource.raid_device}) - not stopping")
+ end
end
end
diff --git a/lib/chef/resource/mount.rb b/lib/chef/resource/mount.rb
index 2d85b3897c..c23ba9bbee 100644
--- a/lib/chef/resource/mount.rb
+++ b/lib/chef/resource/mount.rb
@@ -1,7 +1,7 @@
#
# Author:: Joshua Timberman (<joshua@chef.io>)
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,171 +17,89 @@
# limitations under the License.
#
-require "chef/resource"
+require_relative "../resource"
class Chef
class Resource
class Mount < Chef::Resource
+ description "Use the **mount** resource to manage a mounted file system."
+ unified_mode true
- identity_attr :device
-
- 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
- @mount_point = name
- @device = nil
- @device_type = :device
- @fsck_device = "-"
- @fstype = "auto"
- @options = ["defaults"]
- @dump = 0
- @pass = 2
- @mounted = false
- @enabled = false
- @supports = { :remount => false }
- @username = nil
- @password = nil
- @domain = nil
- end
+ allowed_actions :mount, :umount, :unmount, :remount, :enable, :disable
- def mount_point(arg = nil)
- set_or_return(
- :mount_point,
- arg,
- :kind_of => [ String ]
- )
- end
+ # this is a poor API please do not re-use this pattern
+ property :supports, [Array, Hash],
+ description: "Specify a Hash of supported mount features.",
+ default: lazy { { remount: false } },
+ coerce: proc { |x| x.is_a?(Array) ? x.each_with_object({}) { |i, m| m[i] = true } : x }
- def device(arg = nil)
- set_or_return(
- :device,
- arg,
- :kind_of => [ String ]
- )
- end
+ property :password, String,
+ description: "Windows only:. Use to specify the password for username.",
+ sensitive: true
- def device_type(arg = nil)
- real_arg = arg.kind_of?(String) ? arg.to_sym : arg
- valid_devices = if RUBY_PLATFORM =~ /solaris/i
- [ :device ]
- else
- [ :device, :label, :uuid ]
- end
- set_or_return(
- :device_type,
- real_arg,
- :equal_to => valid_devices
- )
- end
+ property :mount_point, String, name_property: true,
+ coerce: proc { |arg| arg.chomp("/") }, # Removed "/" from the end of str, because it was causing idempotency issue.
+ description: "The directory (or path) in which the device is to be mounted. Defaults to the name of the resource block if not provided."
- def fsck_device(arg = nil)
- set_or_return(
- :fsck_device,
- arg,
- :kind_of => [ String ]
- )
- end
+ property :device, String, identity: true,
+ description: "Required for :umount and :remount actions (for the purpose of checking the mount command output for presence). The special block device or remote node, a label, or a uuid to be mounted."
- def fstype(arg = nil)
- set_or_return(
- :fstype,
- arg,
- :kind_of => [ String ]
- )
- end
+ property :device_type, [String, Symbol],
+ description: "The type of device: :device, :label, or :uuid",
+ coerce: proc { |arg| arg.is_a?(String) ? arg.to_sym : arg },
+ default: :device,
+ equal_to: RUBY_PLATFORM.match?(/solaris/i) ? %i{ device } : %i{ device label uuid }
- def options(arg = nil)
- ret = set_or_return(
- :options,
- arg,
- :kind_of => [ Array, String ]
- )
-
- if ret.is_a? String
- ret.tr(",", " ").split(/ /)
- else
- ret
- end
- end
+ # @todo this should get refactored away: https://github.com/chef/chef/issues/7621
+ property :mounted, [TrueClass, FalseClass], default: false, skip_docs: true
- def dump(arg = nil)
- set_or_return(
- :dump,
- arg,
- :kind_of => [ Integer, FalseClass ]
- )
- end
+ property :fsck_device, String,
+ description: "Solaris only: The fsck device.",
+ default: "-"
- def pass(arg = nil)
- set_or_return(
- :pass,
- arg,
- :kind_of => [ Integer, FalseClass ]
- )
- end
+ property :fstype, [String, nil],
+ description: "The file system type (fstype) of the device.",
+ default: "auto"
- def mounted(arg = nil)
- set_or_return(
- :mounted,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
+ property :options, [Array, String, nil],
+ description: "An array or comma separated list of options for the mount.",
+ coerce: proc { |arg| mount_options(arg) }, # Please see #mount_options method.
+ default: %w{defaults}
- def enabled(arg = nil)
- set_or_return(
- :enabled,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
+ property :dump, [Integer, FalseClass],
+ description: "The dump frequency (in days) used while creating a file systems table (fstab) entry.",
+ default: 0
- def supports(args = {})
- if args.is_a? Array
- args.each { |arg| @supports[arg] = true }
- elsif args.any?
- @supports = args
- else
- @supports
- end
- end
+ property :pass, [Integer, FalseClass],
+ description: "The pass number used by the file system check (fsck) command while creating a file systems table (fstab) entry.",
+ default: 2
- def username(arg = nil)
- set_or_return(
- :username,
- arg,
- :kind_of => [ String ]
- )
- end
+ property :enabled, [TrueClass, FalseClass],
+ description: "Use to specify if a mounted file system is enabled.",
+ default: false
- def password(arg = nil)
- set_or_return(
- :password,
- arg,
- :kind_of => [ String ]
- )
- end
+ property :username, String,
+ description: "Windows only: Use to specify the user name."
- def domain(arg = nil)
- set_or_return(
- :domain,
- arg,
- :kind_of => [ String ]
- )
- end
+ property :domain, String,
+ description: "Windows only: Use to specify the domain in which the `username` and `password` are located."
private
# Used by the AIX provider to set fstype to nil.
- # TODO use property to make nil a valid value for fstype
+ # @todo use property to make nil a valid value for fstype
def clear_fstype
@fstype = nil
end
+ # Returns array of string without leading and trailing whitespace.
+ def mount_options(options)
+ (options.is_a?(String) ? options.split(",") : options).collect(&:strip)
+ end
+
end
end
end
diff --git a/lib/chef/resource/msu_package.rb b/lib/chef/resource/msu_package.rb
new file mode 100644
index 0000000000..23e92b2dd1
--- /dev/null
+++ b/lib/chef/resource/msu_package.rb
@@ -0,0 +1,66 @@
+#
+# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>)
+# Copyright:: Copyright (c) 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_relative "package"
+require_relative "../mixin/uris"
+
+class Chef
+ class Resource
+ class MsuPackage < Chef::Resource::Package
+ include Chef::Mixin::Uris
+ unified_mode true
+
+ provides :msu_package
+
+ description "Use the **msu_package** resource to install Microsoft Update(MSU) packages on Microsoft Windows machines."
+ introduced "12.17"
+
+ allowed_actions :install, :remove
+ default_action :install
+
+ property :package_name, String,
+ description: "An optional property to set the package name if it differs from the resource block's name.",
+ identity: true
+
+ # This is the same property as the main package resource except it has the skip docs set to true
+ # This resource abuses the package resource by storing the versions of all the cabs in the MSU file
+ # in the version attribute from load current value even though those aren't technically the version of the
+ # msu. Since the user wouldn't actually set this we don't want it on the docs site.
+ property :version, [String, Array],
+ skip_docs: true,
+ description: "The version of a package to be installed or upgraded."
+
+ property :source, String,
+ description: "The local file path or URL for the MSU package.",
+ coerce: (proc do |s|
+ unless s.nil?
+ uri_scheme?(s) ? s : Chef::Util::PathHelper.canonical_path(s, false)
+ end
+ end),
+ default: lazy { package_name }
+
+ property :checksum, String, desired_state: false,
+ description: "SHA-256 digest used to verify the checksum of the downloaded MSU package."
+
+ property :timeout, [String, Integer],
+ default: 3600,
+ description: "The amount of time (in seconds) to wait before timing out.",
+ desired_state: false
+ end
+ end
+end
diff --git a/lib/chef/resource/notify_group.rb b/lib/chef/resource/notify_group.rb
new file mode 100644
index 0000000000..9a1edf3eb8
--- /dev/null
+++ b/lib/chef/resource/notify_group.rb
@@ -0,0 +1,74 @@
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class NotifyGroup < Chef::Resource
+ provides :notify_group
+
+ unified_mode true
+
+ description "The notify_group resource does nothing, and always fires notifications which are set on it. Use it to DRY blocks of notifications that are common to multiple resources, and provide a single target for other resources to notify. Unlike most resources, its default action is :nothing."
+ introduced "15.8"
+ examples <<~DOC
+ Wire up a notification from a service resource to stop and start the service with a 60 second delay.
+
+ ```ruby
+ service "crude" do
+ action [ :enable, :start ]
+ end
+
+ chef_sleep "60" do
+ action :nothing
+ end
+
+ # Example code for a hypothetical badly behaved service that requires
+ # 60 seconds between a stop and start in order to restart the service
+ # (due to race conditions, bleeding connections down, resources that only
+ # slowly unlock in the background, or other poor software behaviors that
+ # are sometimes encountered).
+ #
+ notify_group "crude_stop_and_start" do
+ notifies :stop, "service[crude]", :immediately
+ notifies :sleep, "chef_sleep[60]", :immediately
+ notifies :start, "service[crude]", :immediately
+ end
+
+ template "/etc/crude/crude.conf" do
+ source "crude.conf.erb"
+ variables node["crude"]
+ notifies :run, "notify_group[crude_stop_and_start]", :immediately
+ end
+ ```
+ DOC
+
+ action :run do
+ new_resource.updated_by_last_action(true)
+ end
+
+ # This is deliberate. Users should be sending a single notification to a notify_group resource which then
+ # distributes multiple notifications. Having a notify_group run by default is unnecessary indirection and
+ # should be replaced by just running the resources in order. If resources need to be batch run multiple times
+ # per invocation then the resources themselves are not properly declarative idempotent resources, and the user
+ # is attempting to turn Chef into an imperative language using this construct and/or the batch of resources
+ # need to be turned into a custom resource.
+ #
+ default_action :nothing
+ end
+ end
+end
diff --git a/lib/chef/resource/ohai.rb b/lib/chef/resource/ohai.rb
index 09cd22efc5..560a15353a 100644
--- a/lib/chef/resource/ohai.rb
+++ b/lib/chef/resource/ohai.rb
@@ -2,6 +2,7 @@
# Author:: Michael Leinartas (<mleinartas@gmail.com>)
# Author:: Tyler Cloke (<tyler@chef.io>)
# Copyright:: Copyright 2010-2016, Michael Leinartas
+# Copyright:: Copyright (c) Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,36 +18,81 @@
# limitations under the License.
#
+require_relative "../resource"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+require "ohai" unless defined?(Ohai::System)
+
class Chef
class Resource
class Ohai < Chef::Resource
+ unified_mode true
+
+ provides :ohai
- identity_attr :name
+ description "Use the **ohai** resource to reload the Ohai configuration on a node. This allows recipes that change system attributes (like a recipe that adds a user) to refer to those attributes later on during the #{ChefUtils::Dist::Infra::PRODUCT} run."
- state_attrs :plugin
+ examples <<~DOC
+ Reload All Ohai Plugins
+
+ ```ruby
+ ohai 'reload' do
+ action :reload
+ end
+ ```
+
+ Reload A Single Ohai Plugin
+
+ ```ruby
+ ohai 'reload' do
+ plugin 'ipaddress'
+ action :reload
+ end
+ ```
- default_action :reload
+ Reload Ohai after a new user is created
- def initialize(name, run_context = nil)
- super
- @name = name
- @plugin = nil
+ ```ruby
+ ohai 'reload_passwd' do
+ action :nothing
+ plugin 'etc'
end
- def plugin(arg = nil)
- set_or_return(
- :plugin,
- arg,
- :kind_of => [ String ]
- )
+ user 'daemon_user' do
+ home '/dev/null'
+ shell '/sbin/nologin'
+ system true
+ notifies :reload, 'ohai[reload_passwd]', :immediately
end
- def name(arg = nil)
- set_or_return(
- :name,
- arg,
- :kind_of => [ String ]
- )
+ ruby_block 'just an example' do
+ block do
+ # These variables will now have the new values
+ puts node['etc']['passwd']['daemon_user']['uid']
+ puts node['etc']['passwd']['daemon_user']['gid']
+ end
+ end
+ ```
+ DOC
+
+ property :plugin, String,
+ description: "Specific Ohai attribute data to reload. This property behaves similar to specifying attributes when running Ohai on the command line and takes the attribute that you wish to reload instead of the actual plugin name. For instance, you can pass `ipaddress` to reload `node['ipaddress']` even though that data comes from the `Network` plugin. If this property is not specified, #{ChefUtils::Dist::Infra::PRODUCT} will reload all plugins."
+
+ def load_current_resource
+ true
+ end
+
+ action :reload do
+ converge_by("re-run ohai and merge results into node attributes") do
+ ohai = ::Ohai::System.new
+
+ # If new_resource.plugin is nil, ohai will reload all the plugins
+ # Otherwise it will only reload the specified plugin
+ # Note that any changes to plugins, or new plugins placed on
+ # the path are picked up by ohai.
+ ohai.all_plugins new_resource.plugin
+ node.automatic_attrs.merge! ohai.data
+ logger.info("#{new_resource} reloaded")
+ end
end
end
end
diff --git a/lib/chef/resource/ohai_hint.rb b/lib/chef/resource/ohai_hint.rb
new file mode 100644
index 0000000000..88ea02c809
--- /dev/null
+++ b/lib/chef/resource/ohai_hint.rb
@@ -0,0 +1,123 @@
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class OhaiHint < Chef::Resource
+ unified_mode true
+
+ provides(:ohai_hint) { true }
+
+ description "Use the **ohai_hint** resource to aid in configuration detection by passing hint data to Ohai."
+ introduced "14.0"
+ examples <<~DOC
+ **Create a hint file**
+
+ ```ruby
+ ohai_hint 'example' do
+ content a: 'test_content'
+ end
+ ```
+
+ **Create a hint file with a name that does not match the resource name**
+
+ ```ruby
+ ohai_hint 'example' do
+ hint_name 'custom'
+ end
+ ```
+
+ **Create a hint file that is not loaded at compile time**
+
+ ```ruby
+ ohai_hint 'example' do
+ compile_time false
+ end
+ ```
+
+ **Delete a hint file**
+
+ ```ruby
+ ohai_hint 'example' do
+ action :delete
+ end
+ ```
+ DOC
+
+ property :hint_name, String,
+ description: "An optional property to set the hint name if it differs from the resource block's name.",
+ name_property: true
+
+ property :content, Hash,
+ description: "Values to include in the hint file."
+
+ # override compile_time property to default to true
+ property :compile_time, [TrueClass, FalseClass],
+ description: "Determines whether or not the resource is executed during the compile time phase.",
+ default: true, desired_state: false
+
+ action :create do
+ description "Create an Ohai hint file."
+
+ directory ::Ohai::Config.ohai.hints_path.first do
+ action :create
+ recursive true
+ end
+
+ file ohai_hint_file_path(new_resource.hint_name) do
+ action :create
+ content format_content(new_resource.content)
+ end
+ end
+
+ action :delete do
+ description "Delete an Ohai hint file."
+
+ file ohai_hint_file_path(new_resource.hint_name) do
+ action :delete
+ notifies :reload, ohai[reload ohai post hint removal]
+ end
+
+ ohai "reload ohai post hint removal" do
+ action :nothing
+ end
+ end
+
+ action_class do
+ # given a hint filename return the platform specific hint file path
+ # @param filename [String] the name of the hint file
+ # @return [String] absolute path to the file
+ def ohai_hint_file_path(filename)
+ path = ::File.join(::Ohai::Config.ohai.hints_path.first, filename)
+ path << ".json" unless path.end_with?(".json")
+ path
+ end
+
+ # format content hash as JSON
+ # @param content [Hash] the content of the hint file
+ # @return [JSON] json representation of the content of an empty string if content was nil
+ def format_content(content)
+ return "" if content.nil? || content.empty?
+
+ JSON.pretty_generate(content)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/openbsd_package.rb b/lib/chef/resource/openbsd_package.rb
index d0f9fe877f..af632ebb57 100644
--- a/lib/chef/resource/openbsd_package.rb
+++ b/lib/chef/resource/openbsd_package.rb
@@ -2,7 +2,7 @@
# Authors:: AJ Christensen (<aj@chef.io>)
# Richard Manyanza (<liseki@nyikacraftsmen.com>)
# Scott Bonds (<scott@ggr.com>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# Copyright:: Copyright 2014-2016, Richard Manyanza, Scott Bonds
# License:: Apache License, Version 2.0
#
@@ -19,17 +19,25 @@
# limitations under the License.
#
-require "chef/resource/package"
-require "chef/provider/package/openbsd"
-require "chef/mixin/shell_out"
+require_relative "package"
+require_relative "../provider/package/openbsd"
class Chef
class Resource
class OpenbsdPackage < Chef::Resource::Package
- include Chef::Mixin::ShellOut
-
- resource_name :openbsd_package
+ unified_mode true
+ provides :openbsd_package
provides :package, os: "openbsd"
+
+ description "Use the **openbsd_package** resource to manage packages for the OpenBSD platform."
+ introduced "12.1"
+
+ property :package_name, String,
+ description: "An optional property to set the package name if it differs from the resource block's name.",
+ identity: true
+
+ property :version, String,
+ description: "The version of a package to be installed or upgraded."
end
end
end
diff --git a/lib/chef/resource/openssl_dhparam.rb b/lib/chef/resource/openssl_dhparam.rb
new file mode 100644
index 0000000000..3d20b1b439
--- /dev/null
+++ b/lib/chef/resource/openssl_dhparam.rb
@@ -0,0 +1,110 @@
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class OpensslDhparam < Chef::Resource
+ require_relative "../mixin/openssl_helper"
+ include Chef::Mixin::OpenSSLHelper
+
+ unified_mode true
+
+ provides(:openssl_dhparam) { true }
+
+ description "Use the **openssl_dhparam** resource to generate `dhparam.pem` files. If a valid `dhparam.pem` file is found at the specified location, no new file will be created. If a file is found at the specified location but it is not a valid `dhparam.pem` file, it will be overwritten."
+ introduced "14.0"
+ examples <<~DOC
+ **Create a dhparam file**
+
+ ```ruby
+ openssl_dhparam '/etc/httpd/ssl/dhparam.pem'
+ ```
+
+ **Create a dhparam file with a specific key length**
+
+ ```ruby
+ openssl_dhparam '/etc/httpd/ssl/dhparam.pem' do
+ key_length 4096
+ end
+ ```
+
+ **Create a dhparam file with specific user/group ownership**
+
+ ```ruby
+ openssl_dhparam '/etc/httpd/ssl/dhparam.pem' do
+ owner 'www-data'
+ group 'www-data'
+ end
+ ```
+
+ **Manually specify the dhparam file path**
+
+ ```ruby
+ openssl_dhparam 'httpd_dhparam' do
+ path '/etc/httpd/ssl/dhparam.pem'
+ end
+ ```
+ DOC
+
+ property :path, String,
+ description: "An optional property for specifying the path to write the file to if it differs from the resource block's name.",
+ name_property: true
+
+ property :key_length, Integer,
+ equal_to: [1024, 2048, 4096, 8192],
+ validation_message: "key_length must be 1024, 2048, 4096, or 8192.",
+ description: "The desired bit length of the generated key.",
+ default: 2048
+
+ property :generator, Integer,
+ equal_to: [2, 5],
+ validation_message: "generator must be either 2 or 5.",
+ description: "The desired Diffie-Hellmann generator.",
+ default: 2
+
+ property :owner, [String, Integer],
+ description: "The owner applied to all files created by the resource."
+
+ property :group, [String, Integer],
+ description: "The group ownership applied to all files created by the resource."
+
+ property :mode, [Integer, String],
+ description: "The permission mode applied to all files created by the resource.",
+ default: "0640"
+
+ action :create do
+ description "Create the dhparam file."
+ dhparam_content = nil
+ unless dhparam_pem_valid?(new_resource.path)
+ dhparam_content = gen_dhparam(new_resource.key_length, new_resource.generator).to_pem
+ Chef::Log.debug("Valid dhparam content not found at #{new_resource.path}, creating new")
+ end
+
+ file new_resource.path do
+ action :create
+ owner new_resource.owner
+ group new_resource.group
+ mode new_resource.mode
+ sensitive true
+ content dhparam_content
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/openssl_ec_private_key.rb b/lib/chef/resource/openssl_ec_private_key.rb
new file mode 100644
index 0000000000..7625b5ea6e
--- /dev/null
+++ b/lib/chef/resource/openssl_ec_private_key.rb
@@ -0,0 +1,119 @@
+#
+# Copyright:: Copyright (c) Chef Software Inc.
+# Author:: Julien Huon
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../resource"
+
+class Chef
+ class Resource
+ class OpensslEcPrivateKey < Chef::Resource
+ require_relative "../mixin/openssl_helper"
+ include Chef::Mixin::OpenSSLHelper
+
+ unified_mode true
+
+ provides :openssl_ec_private_key
+
+ description "Use the **openssl_ec_private_key** resource to generate an elliptic curve (EC) private key file. If a valid EC key file can be opened at the specified location, no new file will be created. If the EC key file cannot be opened, either because it does not exist or because the password to the EC key file does not match the password in the recipe, then it will be overwritten."
+ introduced "14.4"
+ examples <<~DOC
+ Generate a new ec privatekey with prime256v1 key curve and default des3 cipher
+
+ ```ruby
+ openssl_ec_private_key '/etc/ssl_files/eckey_prime256v1_des3.pem' do
+ key_curve 'prime256v1'
+ key_pass 'something'
+ action :create
+ end
+ ```
+
+ Generate a new ec private key with prime256v1 key curve and aes-128-cbc cipher
+
+ ```ruby
+ openssl_ec_private_key '/etc/ssl_files/eckey_prime256v1_des3.pem' do
+ key_curve 'prime256v1'
+ key_cipher 'aes-128-cbc'
+ key_pass 'something'
+ action :create
+ end
+ ```
+ DOC
+
+ property :path, String,
+ description: "An optional property for specifying the path to write the file to if it differs from the resource block's name.",
+ name_property: true
+
+ property :key_curve, String,
+ equal_to: %w{secp384r1 secp521r1 prime256v1 secp224r1 secp256k1},
+ description: "The desired curve of the generated key (if key_type is equal to 'ec'). Run openssl ecparam -list_curves to see available options.",
+ default: "prime256v1"
+
+ property :key_pass, String,
+ description: "The desired passphrase for the key."
+
+ property :key_cipher, String,
+ description: "The designed cipher to use when generating your key. Run `openssl list-cipher-algorithms` to see available options.",
+ default: lazy { "des3" },
+ default_description: "des3",
+ callbacks: {
+ "key_cipher must be a cipher known to openssl. Run `openssl list-cipher-algorithms` to see available options." =>
+ proc { |v| OpenSSL::Cipher.ciphers.include?(v) },
+ }
+
+ property :owner, [String, Integer],
+ description: "The owner applied to all files created by the resource."
+
+ property :group, [String, Integer],
+ description: "The group ownership applied to all files created by the resource."
+
+ property :mode, [Integer, String],
+ description: "The permission mode applied to all files created by the resource.",
+ default: "0600"
+
+ property :force, [TrueClass, FalseClass],
+ description: "Force creation of the key even if the same key already exists on the node.",
+ default: false, desired_state: false
+
+ action :create do
+ description "Generate the ec private key"
+
+ unless new_resource.force || priv_key_file_valid?(new_resource.path, new_resource.key_pass)
+ converge_by("Create an EC private key #{new_resource.path}") do
+ log "Generating an #{new_resource.key_curve} "\
+ "EC key file at #{new_resource.path}, this may take some time"
+
+ if new_resource.key_pass
+ unencrypted_ec_key = gen_ec_priv_key(new_resource.key_curve)
+ ec_key_content = encrypt_ec_key(unencrypted_ec_key, new_resource.key_pass, new_resource.key_cipher)
+ else
+ ec_key_content = gen_ec_priv_key(new_resource.key_curve).to_pem
+ end
+
+ file new_resource.path do
+ action :create
+ owner new_resource.owner unless new_resource.owner.nil?
+ group new_resource.group unless new_resource.group.nil?
+ mode new_resource.mode
+ sensitive true
+ content ec_key_content
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/openssl_ec_public_key.rb b/lib/chef/resource/openssl_ec_public_key.rb
new file mode 100644
index 0000000000..44441eb72d
--- /dev/null
+++ b/lib/chef/resource/openssl_ec_public_key.rb
@@ -0,0 +1,96 @@
+#
+# Copyright:: Copyright (c) Chef Software Inc.
+# Author:: Julien Huon
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../resource"
+
+class Chef
+ class Resource
+ class OpensslEcPublicKey < Chef::Resource
+ require_relative "../mixin/openssl_helper"
+ include Chef::Mixin::OpenSSLHelper
+
+ unified_mode true
+
+ provides :openssl_ec_public_key
+
+ description "Use the **openssl_ec_public_key** resource to generate elliptic curve (EC) public key files from a given EC private key."
+ introduced "14.4"
+ examples <<~DOC
+ **Generate new EC public key from a private key on disk**
+
+ ```ruby
+ openssl_ec_public_key '/etc/ssl_files/eckey_prime256v1_des3.pub' do
+ private_key_path '/etc/ssl_files/eckey_prime256v1_des3.pem'
+ private_key_pass 'something'
+ action :create
+ end
+ ```
+
+ **Generate new EC public key by passing in a private key**
+
+ ```ruby
+ openssl_ec_public_key '/etc/ssl_files/eckey_prime256v1_des3_2.pub' do
+ private_key_content "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEII2VAU9re44mAUzYPWCg+qqwdmP8CplsEg0b/DYPXLg2oAoGCCqGSM49\nAwEHoUQDQgAEKkpMCbIQ2C6Qlp/B+Odp1a9Y06Sm8yqPvCVIkWYP7M8PX5+RmoIv\njGBVf/+mVBx77ji3NpTilMUt2KPZ87lZ3w==\n-----END EC PRIVATE KEY-----\n"
+ action :create
+ end
+ ```
+ DOC
+
+ property :path, String,
+ description: "An optional property for specifying the path to write the file to if it differs from the resource block's name.",
+ name_property: true
+
+ property :private_key_path, String,
+ description: "The path to the private key file."
+
+ property :private_key_content, String,
+ description: "The content of the private key including new lines. This property is used in place of private_key_path in instances where you want to avoid having to first write the private key to disk"
+
+ property :private_key_pass, String,
+ description: "The passphrase of the provided private key."
+
+ property :owner, [String, Integer],
+ description: "The owner applied to all files created by the resource."
+
+ property :group, [String, Integer],
+ description: "The group ownership applied to all files created by the resource."
+
+ property :mode, [Integer, String],
+ description: "The permission mode applied to all files created by the resource.",
+ default: "0640"
+
+ action :create do
+ description "Generate the ec public key from a private key"
+
+ raise ArgumentError, "You cannot specify both 'private_key_path' and 'private_key_content' properties at the same time." if new_resource.private_key_path && new_resource.private_key_content
+ raise ArgumentError, "You must specify the private key with either 'private_key_path' or 'private_key_content' properties." unless new_resource.private_key_path || new_resource.private_key_content
+ raise "#{new_resource.private_key_path} not a valid private EC key or password is invalid" unless priv_key_file_valid?((new_resource.private_key_path || new_resource.private_key_content), new_resource.private_key_pass)
+
+ ec_key_content = gen_ec_pub_key((new_resource.private_key_path || new_resource.private_key_content), new_resource.private_key_pass)
+
+ file new_resource.path do
+ action :create
+ owner new_resource.owner unless new_resource.owner.nil?
+ group new_resource.group unless new_resource.group.nil?
+ mode new_resource.mode
+ content ec_key_content
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/openssl_rsa_private_key.rb b/lib/chef/resource/openssl_rsa_private_key.rb
new file mode 100644
index 0000000000..e9e6ef24ca
--- /dev/null
+++ b/lib/chef/resource/openssl_rsa_private_key.rb
@@ -0,0 +1,115 @@
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class OpensslRsaPrivateKey < Chef::Resource
+ require_relative "../mixin/openssl_helper"
+ include Chef::Mixin::OpenSSLHelper
+
+ unified_mode true
+
+ provides(:openssl_rsa_private_key) { true }
+ provides(:openssl_rsa_key) { true } # legacy cookbook resource name
+
+ description "Use the **openssl_rsa_private_key** resource to generate RSA private key files. If a valid RSA key file can be opened at the specified location, no new file will be created. If the RSA key file cannot be opened, either because it does not exist or because the password to the RSA key file does not match the password in the recipe, it will be overwritten."
+ introduced "14.0"
+ examples <<~DOC
+ Generate new 2048bit key with the default des3 cipher
+
+ ```ruby
+ openssl_rsa_private_key '/etc/ssl_files/rsakey_des3.pem' do
+ key_length 2048
+ action :create
+ end
+ ```
+
+ Generate new 1024bit key with the aes-128-cbc cipher
+
+ ```ruby
+ openssl_rsa_key '/etc/ssl_files/rsakey_aes128cbc.pem' do
+ key_length 1024
+ key_cipher 'aes-128-cbc'
+ action :create
+ end
+ ```
+ DOC
+
+ property :path, String,
+ description: "An optional property for specifying the path to write the file to if it differs from the resource block's name.",
+ name_property: true
+
+ property :key_length, Integer,
+ equal_to: [1024, 2048, 4096, 8192],
+ validation_message: "key_length (bits) must be 1024, 2048, 4096, or 8192!",
+ description: "The desired bit length of the generated key.",
+ default: 2048
+
+ property :key_pass, String,
+ description: "The desired passphrase for the key."
+
+ property :key_cipher, String,
+ description: "The designed cipher to use when generating your key. Run `openssl list-cipher-algorithms` to see available options.",
+ default: lazy { "des3" },
+ default_description: "des3",
+ callbacks: {
+ "key_cipher must be a cipher known to openssl. Run `openssl list-cipher-algorithms` to see available options." =>
+ proc { |v| OpenSSL::Cipher.ciphers.include?(v) },
+ }
+
+ property :owner, [String, Integer],
+ description: "The owner applied to all files created by the resource."
+
+ property :group, [String, Integer],
+ description: "The group ownership applied to all files created by the resource."
+
+ property :mode, [Integer, String],
+ description: "The permission mode applied to all files created by the resource.",
+ default: "0600"
+
+ property :force, [TrueClass, FalseClass],
+ description: "Force creation of the key even if the same key already exists on the node.",
+ default: false, desired_state: false
+
+ action :create do
+ description "Create the RSA private key."
+
+ return if new_resource.force || priv_key_file_valid?(new_resource.path, new_resource.key_pass)
+
+ converge_by("create #{new_resource.key_length} bit RSA key #{new_resource.path}") do
+ if new_resource.key_pass
+ unencrypted_rsa_key = gen_rsa_priv_key(new_resource.key_length)
+ rsa_key_content = encrypt_rsa_key(unencrypted_rsa_key, new_resource.key_pass, new_resource.key_cipher)
+ else
+ rsa_key_content = gen_rsa_priv_key(new_resource.key_length).to_pem
+ end
+
+ file new_resource.path do
+ action :create
+ owner new_resource.owner unless new_resource.owner.nil?
+ group new_resource.group unless new_resource.group.nil?
+ mode new_resource.mode
+ sensitive true
+ content rsa_key_content
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/openssl_rsa_public_key.rb b/lib/chef/resource/openssl_rsa_public_key.rb
new file mode 100644
index 0000000000..8fd8ab558e
--- /dev/null
+++ b/lib/chef/resource/openssl_rsa_public_key.rb
@@ -0,0 +1,97 @@
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class OpensslRsaPublicKey < Chef::Resource
+ require_relative "../mixin/openssl_helper"
+ include Chef::Mixin::OpenSSLHelper
+
+ unified_mode true
+
+ provides(:openssl_rsa_public_key) { true }
+
+ examples <<~DOC
+ Generate new public key from a private key on disk
+
+ ```ruby
+ openssl_rsa_public_key '/etc/ssl_files/rsakey_des3.pub' do
+ private_key_path '/etc/ssl_files/rsakey_des3.pem'
+ private_key_pass 'something'
+ action :create
+ end
+ ```
+
+ Generate new public key by passing in a private key
+
+ ```ruby
+ openssl_rsa_public_key '/etc/ssl_files/rsakey_2.pub' do
+ private_key_pass 'something'
+ private_key_content "-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC,5EE0AE9A5FE3342E\n\nyb930kj5/4/nd738dPx6XdbDrMCvqkldaz0rHNw8xsWvwARrl/QSPwROG3WY7ROl\nEUttVlLaeVaqRPfQbmTUfzGI8kTMmDWKjw52gJUx2YJTYRgMHAB0dzYIRjeZAaeS\nypXnEfouVav+jKTmmehr1WuVKbzRhQDBSalzeUwsPi2+fb3Bfuo1dRW6xt8yFuc4\nAkv1hCglymPzPHE2L0nSGjcgA2DZu+/S8/wZ4E63442NHPzO4VlLvpNvJrYpEWq9\nB5mJzcdXPeOTjqd13olNTlOZMaKxu9QShu50GreCTVsl8VRkK8NtwbWuPGBZlIFa\njzlS/RaLuzNzfajaKMkcIYco9t7gN2DwnsACHKqEYT8248Ii3NQ+9/M5YcmpywQj\nWGr0UFCSAdCky1lRjwT+zGQKohr+dVR1GaLem+rSZH94df4YBxDYw4rjsKoEhvXB\nv2Vlx+G7Vl2NFiZzxUKh3MvQLr/NDElpG1pYWDiE0DIG13UqEG++cS870mcEyfFh\nSF2SXYHLWyAhDK0viRDChJyFMduC4E7a2P9DJhL3ZvM0KZ1SLMwROc1XuZ704GwO\nYUqtCX5OOIsTti1Z74jQm9uWFikhgWByhVtu6sYL1YTqtiPJDMFhA560zp/k/qLO\nFKiM4eUWV8AI8AVwT6A4o45N2Ru8S48NQyvh/ADFNrgJbVSeDoYE23+DYKpzbaW9\n00BD/EmUQqaQMc670vmI+CIdcdE7L1zqD6MZN7wtPaRIjx4FJBGsFoeDShr+LoTD\nrwbadwrbc2Rf4DWlvFwLJ4pvNvdtY3wtBu79UCOol0+t8DVVSPVASsh+tp8XncDE\nKRljj88WwBjX7/YlRWvQpe5y2UrsHI0pNy8TA1Xkf6GPr6aS2TvQD5gOrAVReSse\n/kktCzZQotjmY1odvo90Zi6A9NCzkI4ZLgAuhiKDPhxZg61IeLppnfFw0v3H4331\nV9SMYgr1Ftov0++x7q9hFPIHwZp6NHHOhdHNI80XkHqtY/hEvsh7MhFMYCgSY1pa\nK/gMcZ/5Wdg9LwOK6nYRmtPtg6fuqj+jB3Rue5/p9dt4kfom4etCSeJPdvP1Mx2I\neNmyQ/7JN9N87FsfZsIj5OK9OB0fPdj0N0m1mlHM/mFt5UM5x39u13QkCt7skEF+\nyOptXcL629/xwm8eg4EXnKFk330WcYSw+sYmAQ9ZTsBxpCMkz0K4PBTPWWXx63XS\nc4J0r88kbCkMCNv41of8ceeGzFrC74dG7i3IUqZzMzRP8cFeps8auhweUHD2hULs\nXwwtII0YQ6/Fw4hgGQ5//0ASdvAicvH0l1jOQScHzXC2QWNg3GttueB/kmhMeGGm\nsHOJ1rXQ4oEckFvBHOvzjP3kuRHSWFYDx35RjWLAwLCG9odQUApHjLBgFNg9yOR0\njW9a2SGxRvBAfdjTa9ZBBrbjlaF57hq7mXws90P88RpAL+xxCAZUElqeW2Rb2rQ6\nCbz4/AtPekV1CYVodGkPutOsew2zjNqlNH+M8XzfonA60UAH20TEqAgLKwgfgr+a\nc+rXp1AupBxat4EHYJiwXBB9XcVwyp5Z+/dXsYmLXzoMOnp8OFyQ9H8R7y9Y0PEu\n-----END RSA PRIVATE KEY-----\n"
+ action :create
+ end
+ ```
+ DOC
+
+ description "Use the **openssl_rsa_public_key** resource to generate RSA public key files for a given RSA private key."
+ introduced "14.0"
+
+ property :path, String,
+ description: "An optional property for specifying the path to the public key if it differs from the resource block's name.",
+ name_property: true
+
+ property :private_key_path, String,
+ description: "The path to the private key file."
+
+ property :private_key_content, String,
+ description: "The content of the private key, including new lines. This property is used in place of private_key_path in instances where you want to avoid having to first write the private key to disk."
+
+ property :private_key_pass, String,
+ description: "The passphrase of the provided private key."
+
+ property :owner, [String, Integer],
+ description: "The owner applied to all files created by the resource."
+
+ property :group, [String, Integer],
+ description: "The group ownership applied to all files created by the resource."
+
+ property :mode, [Integer, String],
+ description: "The permission mode applied to all files created by the resource.",
+ default: "0640"
+
+ action :create do
+ description "Create the RSA public key."
+
+ raise ArgumentError, "You cannot specify both 'private_key_path' and 'private_key_content' properties at the same time." if new_resource.private_key_path && new_resource.private_key_content
+ raise ArgumentError, "You must specify the private key with either 'private_key_path' or 'private_key_content' properties." unless new_resource.private_key_path || new_resource.private_key_content
+ raise "#{new_resource.private_key_path} not a valid private RSA key or password is invalid" unless priv_key_file_valid?((new_resource.private_key_path || new_resource.private_key_content), new_resource.private_key_pass)
+
+ rsa_key_content = gen_rsa_pub_key((new_resource.private_key_path || new_resource.private_key_content), new_resource.private_key_pass)
+
+ file new_resource.path do
+ action :create
+ owner new_resource.owner unless new_resource.owner.nil?
+ group new_resource.group unless new_resource.group.nil?
+ mode new_resource.mode
+ content rsa_key_content
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/openssl_x509_certificate.rb b/lib/chef/resource/openssl_x509_certificate.rb
new file mode 100644
index 0000000000..c723f47d61
--- /dev/null
+++ b/lib/chef/resource/openssl_x509_certificate.rb
@@ -0,0 +1,267 @@
+#
+# License:: Apache License, Version 2.0
+# Author:: Julien Huon
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class OpensslX509Certificate < Chef::Resource
+ require_relative "../mixin/openssl_helper"
+ include Chef::Mixin::OpenSSLHelper
+
+ unified_mode true
+
+ provides :openssl_x509_certificate
+ provides(:openssl_x509) { true } # legacy cookbook name.
+
+ description "Use the **openssl_x509_certificate** resource to generate signed or self-signed, PEM-formatted x509 certificates. If no existing key is specified, the resource will automatically generate a passwordless key with the certificate. If a CA private key and certificate are provided, the certificate will be signed with them. Note: This resource was renamed from openssl_x509 to openssl_x509_certificate. The legacy name will continue to function, but cookbook code should be updated for the new resource name."
+ introduced "14.4"
+ examples <<~DOC
+ Create a simple self-signed certificate file
+
+ ```ruby
+ openssl_x509_certificate '/etc/httpd/ssl/mycert.pem' do
+ common_name 'www.f00bar.com'
+ org 'Foo Bar'
+ org_unit 'Lab'
+ country 'US'
+ end
+ ```
+
+ Create a certificate using additional options
+
+ ```ruby
+ openssl_x509_certificate '/etc/ssl_files/my_signed_cert.crt' do
+ common_name 'www.f00bar.com'
+ ca_key_file '/etc/ssl_files/my_ca.key'
+ ca_cert_file '/etc/ssl_files/my_ca.crt'
+ expire 365
+ extensions(
+ 'keyUsage' => {
+ 'values' => %w(
+ keyEncipherment
+ digitalSignature),
+ 'critical' => true,
+ },
+ 'extendedKeyUsage' => {
+ 'values' => %w(serverAuth),
+ 'critical' => false,
+ }
+ )
+ subject_alt_name ['IP:127.0.0.1', 'DNS:localhost.localdomain']
+ end
+ ```
+ DOC
+
+ property :path, String,
+ description: "An optional property for specifying the path to write the file to if it differs from the resource block's name.",
+ name_property: true
+
+ property :owner, [String, Integer],
+ description: "The owner applied to all files created by the resource."
+
+ property :group, [String, Integer],
+ description: "The group ownership applied to all files created by the resource."
+
+ property :expire, Integer,
+ description: "Value representing the number of days from now through which the issued certificate cert will remain valid. The certificate will expire after this period.",
+ default: 365
+
+ property :mode, [Integer, String],
+ description: "The permission mode applied to all files created by the resource."
+
+ property :country, String,
+ description: "Value for the `C` certificate field."
+
+ property :state, String,
+ description: "Value for the `ST` certificate field."
+
+ property :city, String,
+ description: "Value for the `L` certificate field."
+
+ property :org, String,
+ description: "Value for the `O` certificate field."
+
+ property :org_unit, String,
+ description: "Value for the `OU` certificate field."
+
+ property :common_name, String,
+ description: "Value for the `CN` certificate field."
+
+ property :email, String,
+ description: "Value for the `email` certificate field."
+
+ property :extensions, Hash,
+ description: "Hash of X509 Extensions entries, in format `{ 'keyUsage' => { 'values' => %w( keyEncipherment digitalSignature), 'critical' => true } }`.",
+ default: lazy { {} }
+
+ property :subject_alt_name, Array,
+ description: "Array of Subject Alternative Name entries, in format `DNS:example.com` or `IP:1.2.3.4`.",
+ default: lazy { [] }
+
+ property :key_file, String,
+ description: "The path to a certificate key file on the filesystem. If the key_file property is specified, the resource will attempt to source a key from this location. If no key file is found, the resource will generate a new key file at this location. If the key_file property is not specified, the resource will generate a key file in the same directory as the generated certificate, with the same name as the generated certificate."
+
+ property :key_pass, String,
+ description: "The passphrase for an existing key's passphrase."
+
+ property :key_type, String,
+ equal_to: %w{rsa ec},
+ description: "The desired type of the generated key.",
+ default: "rsa"
+
+ property :key_length, Integer,
+ equal_to: [1024, 2048, 4096, 8192],
+ description: "The desired bit length of the generated key (if key_type is equal to 'rsa').",
+ default: 2048
+
+ property :key_curve, String,
+ description: "The desired curve of the generated key (if key_type is equal to 'ec'). Run `openssl ecparam -list_curves` to see available options.",
+ equal_to: %w{secp384r1 secp521r1 prime256v1},
+ default: "prime256v1"
+
+ property :csr_file, String,
+ description: "The path to a X509 Certificate Request (CSR) on the filesystem. If the `csr_file` property is specified, the resource will attempt to source a CSR from this location. If no CSR file is found, the resource will generate a Self-Signed Certificate and the certificate fields must be specified (common_name at last)."
+
+ property :ca_cert_file, String,
+ description: "The path to the CA X509 Certificate on the filesystem. If the `ca_cert_file` property is specified, the `ca_key_file` property must also be specified, the certificate will be signed with them."
+
+ property :ca_key_file, String,
+ description: "The path to the CA private key on the filesystem. If the `ca_key_file` property is specified, the `ca_cert_file` property must also be specified, the certificate will be signed with them."
+
+ property :ca_key_pass, String,
+ description: "The passphrase for CA private key's passphrase."
+
+ property :renew_before_expiry, Integer,
+ description: "The number of days before the expiry. The certificate will be automatically renewed when the value is reached.",
+ introduced: "15.7"
+
+ action :create do
+ description "Generate a certificate"
+
+ file new_resource.path do
+ action :create_if_missing
+ owner new_resource.owner unless new_resource.owner.nil?
+ group new_resource.group unless new_resource.group.nil?
+ mode new_resource.mode unless new_resource.mode.nil?
+ sensitive true
+ content cert.to_pem
+ end
+
+ if !new_resource.renew_before_expiry.nil? && cert_need_renewal?(new_resource.path, new_resource.renew_before_expiry)
+ file new_resource.path do
+ action :create
+ owner new_resource.owner unless new_resource.owner.nil?
+ group new_resource.group unless new_resource.group.nil?
+ mode new_resource.mode unless new_resource.mode.nil?
+ sensitive true
+ content cert.to_pem
+ end
+ end
+
+ if new_resource.csr_file.nil?
+ file key_file do
+ action :create_if_missing
+ owner new_resource.owner unless new_resource.owner.nil?
+ group new_resource.group unless new_resource.group.nil?
+ mode new_resource.mode unless new_resource.mode.nil?
+ sensitive true
+ content key.to_pem
+ end
+ end
+ end
+
+ action_class do
+ def key_file
+ @key_file ||=
+ if new_resource.key_file
+ new_resource.key_file
+ else
+ path, file = ::File.split(new_resource.path)
+ filename = ::File.basename(file, ::File.extname(file))
+ path + "/" + filename + ".key"
+ end
+ end
+
+ def key
+ @key ||= if priv_key_file_valid?(key_file, new_resource.key_pass)
+ OpenSSL::PKey.read ::File.read(key_file), new_resource.key_pass
+ elsif new_resource.key_type == "rsa"
+ gen_rsa_priv_key(new_resource.key_length)
+ else
+ gen_ec_priv_key(new_resource.key_curve)
+ end
+ end
+
+ def request
+ if new_resource.csr_file.nil?
+ gen_x509_request(subject, key)
+ else
+ OpenSSL::X509::Request.new ::File.read(new_resource.csr_file)
+ end
+ end
+
+ def subject
+ OpenSSL::X509::Name.new.tap do |csr_subject|
+ csr_subject.add_entry("C", new_resource.country) unless new_resource.country.nil?
+ csr_subject.add_entry("ST", new_resource.state) unless new_resource.state.nil?
+ csr_subject.add_entry("L", new_resource.city) unless new_resource.city.nil?
+ csr_subject.add_entry("O", new_resource.org) unless new_resource.org.nil?
+ csr_subject.add_entry("OU", new_resource.org_unit) unless new_resource.org_unit.nil?
+ csr_subject.add_entry("CN", new_resource.common_name)
+ csr_subject.add_entry("emailAddress", new_resource.email) unless new_resource.email.nil?
+ end
+ end
+
+ def ca_private_key
+ if new_resource.csr_file.nil?
+ key
+ else
+ OpenSSL::PKey.read ::File.read(new_resource.ca_key_file), new_resource.ca_key_pass
+ end
+ end
+
+ def ca_info
+ # Will contain issuer (if any) & expiration
+ ca_info = {}
+
+ unless new_resource.ca_cert_file.nil?
+ ca_info["issuer"] = OpenSSL::X509::Certificate.new ::File.read(new_resource.ca_cert_file)
+ end
+ ca_info["validity"] = new_resource.expire
+
+ ca_info
+ end
+
+ def extensions
+ extensions = gen_x509_extensions(new_resource.extensions)
+
+ unless new_resource.subject_alt_name.empty?
+ extensions += gen_x509_extensions("subjectAltName" => { "values" => new_resource.subject_alt_name, "critical" => false })
+ end
+
+ extensions
+ end
+
+ def cert
+ gen_x509_cert(request, extensions, ca_info, ca_private_key)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/openssl_x509_crl.rb b/lib/chef/resource/openssl_x509_crl.rb
new file mode 100644
index 0000000000..6e7f905084
--- /dev/null
+++ b/lib/chef/resource/openssl_x509_crl.rb
@@ -0,0 +1,152 @@
+#
+# License:: Apache License, Version 2.0
+# Author:: Julien Huon
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class OpensslX509Crl < Chef::Resource
+ require_relative "../mixin/openssl_helper"
+ include Chef::Mixin::OpenSSLHelper
+
+ unified_mode true
+
+ provides :openssl_x509_crl
+
+ description "Use the **openssl_x509_crl** resource to generate PEM-formatted x509 certificate revocation list (CRL) files."
+ introduced "14.4"
+ examples <<~DOC
+ **Create a certificate revocation file**
+
+ ```ruby
+ openssl_x509_crl '/etc/ssl_test/my_ca.crl' do
+ ca_cert_file '/etc/ssl_test/my_ca.crt'
+ ca_key_file '/etc/ssl_test/my_ca.key'
+ end
+ ```
+
+ **Create a certificate revocation file for a particular serial**
+
+ ```ruby
+ openssl_x509_crl '/etc/ssl_test/my_ca.crl' do
+ ca_cert_file '/etc/ssl_test/my_ca.crt'
+ ca_key_file '/etc/ssl_test/my_ca.key'
+ serial_to_revoke C7BCB6602A2E4251EF4E2827A228CB52BC0CEA2F
+ end
+ ```
+ DOC
+
+ property :path, String,
+ description: "An optional property for specifying the path to write the file to if it differs from the resource block's name.",
+ name_property: true
+
+ property :serial_to_revoke, [Integer, String],
+ description: "Serial of the X509 Certificate to revoke."
+
+ property :revocation_reason, Integer,
+ description: "Reason for the revocation.",
+ default: 0
+
+ property :expire, Integer,
+ description: "Value representing the number of days from now through which the issued CRL will remain valid. The CRL will expire after this period.",
+ default: 8
+
+ property :renewal_threshold, Integer,
+ description: "Number of days before the expiration. It this threshold is reached, the CRL will be renewed.",
+ default: 1
+
+ property :ca_cert_file, String,
+ description: "The path to the CA X509 Certificate on the filesystem. If the `ca_cert_file` property is specified, the `ca_key_file` property must also be specified, the CRL will be signed with them.",
+ required: true
+
+ property :ca_key_file, String,
+ description: "The path to the CA private key on the filesystem. If the `ca_key_file` property is specified, the `ca_cert_file` property must also be specified, the CRL will be signed with them.",
+ required: true
+
+ property :ca_key_pass, String,
+ description: "The passphrase for CA private key's passphrase."
+
+ property :owner, [String, Integer],
+ description: "The owner permission for the CRL file."
+
+ property :group, [String, Integer],
+ description: "The group permission for the CRL file."
+
+ property :mode, [Integer, String],
+ description: "The permission mode of the CRL file."
+
+ action :create do
+ description "Create the CRL file."
+
+ file new_resource.path do
+ owner new_resource.owner unless new_resource.owner.nil?
+ group new_resource.group unless new_resource.group.nil?
+ mode new_resource.mode unless new_resource.mode.nil?
+ content crl.to_pem
+ action :create
+ end
+ end
+
+ action_class do
+ def crl_info
+ # Will contain issuer & expiration
+ crl_info = {}
+
+ crl_info["issuer"] = ::OpenSSL::X509::Certificate.new ::File.read(new_resource.ca_cert_file)
+ crl_info["validity"] = new_resource.expire
+
+ crl_info
+ end
+
+ def revoke_info
+ # Will contain Serial to revoke & reason
+ revoke_info = {}
+
+ revoke_info["serial"] = new_resource.serial_to_revoke
+ revoke_info["reason"] = new_resource.revocation_reason
+
+ revoke_info
+ end
+
+ def ca_private_key
+ ::OpenSSL::PKey.read ::File.read(new_resource.ca_key_file), new_resource.ca_key_pass
+ end
+
+ def crl
+ if crl_file_valid?(new_resource.path)
+ crl = ::OpenSSL::X509::CRL.new ::File.read(new_resource.path)
+ else
+ log "Creating a CRL #{new_resource.path} for CA #{new_resource.ca_cert_file}"
+ crl = gen_x509_crl(ca_private_key, crl_info)
+ end
+
+ if !new_resource.serial_to_revoke.nil? && serial_revoked?(crl, new_resource.serial_to_revoke) == false
+ log "Revoking serial #{new_resource.serial_to_revoke} in CRL #{new_resource.path}"
+ crl = revoke_x509_crl(revoke_info, crl, ca_private_key, crl_info)
+ elsif crl.next_update <= Time.now + 3600 * 24 * new_resource.renewal_threshold
+ log "Renewing CRL for CA #{new_resource.ca_cert_file}"
+ crl = renew_x509_crl(crl, ca_private_key, crl_info)
+ end
+
+ crl
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/resource/openssl_x509_request.rb b/lib/chef/resource/openssl_x509_request.rb
new file mode 100644
index 0000000000..0e68337b05
--- /dev/null
+++ b/lib/chef/resource/openssl_x509_request.rb
@@ -0,0 +1,187 @@
+#
+# License:: Apache License, Version 2.0
+# Author:: Julien Huon
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class OpensslX509Request < Chef::Resource
+ require_relative "../mixin/openssl_helper"
+ include Chef::Mixin::OpenSSLHelper
+
+ unified_mode true
+
+ provides :openssl_x509_request
+
+ description "Use the **openssl_x509_request** resource to generate PEM-formatted x509 certificates requests. If no existing key is specified, the resource will automatically generate a passwordless key with the certificate."
+ introduced "14.4"
+ examples <<~DOC
+ **Generate new EC key and CSR file**
+
+ ```ruby
+ openssl_x509_request '/etc/ssl_files/my_ec_request.csr' do
+ common_name 'myecrequest.example.com'
+ org 'Test Kitchen Example'
+ org_unit 'Kitchens'
+ country 'UK'
+ end
+ ```
+
+ **Generate a new CSR file from an existing EC key**
+
+ ```ruby
+ openssl_x509_request '/etc/ssl_files/my_ec_request2.csr' do
+ common_name 'myecrequest2.example.com'
+ org 'Test Kitchen Example'
+ org_unit 'Kitchens'
+ country 'UK'
+ key_file '/etc/ssl_files/my_ec_request.key'
+ end
+ ```
+
+ **Generate new RSA key and CSR file**
+
+ ```ruby
+ openssl_x509_request '/etc/ssl_files/my_rsa_request.csr' do
+ common_name 'myrsarequest.example.com'
+ org 'Test Kitchen Example'
+ org_unit 'Kitchens'
+ country 'UK'
+ key_type 'rsa'
+ end
+ ```
+ DOC
+
+ property :path, String, name_property: true,
+ description: "An optional property for specifying the path to write the file to if it differs from the resource block's name."
+
+ property :owner, [String, Integer],
+ description: "The owner applied to all files created by the resource."
+
+ property :group, [String, Integer],
+ description: "The group ownership applied to all files created by the resource."
+
+ property :mode, [Integer, String],
+ description: "The permission mode applied to all files created by the resource."
+
+ property :country, String,
+ description: "Value for the `C` certificate field."
+
+ property :state, String,
+ description: "Value for the `ST` certificate field."
+
+ property :city, String,
+ description: "Value for the `L` certificate field."
+
+ property :org, String,
+ description: "Value for the `O` certificate field."
+
+ property :org_unit, String,
+ description: "Value for the `OU` certificate field."
+
+ property :common_name, String,
+ required: true,
+ description: "Value for the `CN` certificate field."
+
+ property :email, String,
+ description: "Value for the `email` certificate field."
+
+ property :key_file, String,
+ description: "The path to a certificate key file on the filesystem. If the `key_file` property is specified, the resource will attempt to source a key from this location. If no key file is found, the resource will generate a new key file at this location. If the `key_file` property is not specified, the resource will generate a key file in the same directory as the generated certificate, with the same name as the generated certificate."
+
+ property :key_pass, String,
+ description: "The passphrase for an existing key's passphrase."
+
+ property :key_type, String,
+ equal_to: %w{rsa ec}, default: "ec",
+ description: "The desired type of the generated key."
+
+ property :key_length, Integer,
+ equal_to: [1024, 2048, 4096, 8192], default: 2048,
+ description: "The desired bit length of the generated key (if key_type is equal to `rsa`)."
+
+ property :key_curve, String,
+ equal_to: %w{secp384r1 secp521r1 prime256v1}, default: "prime256v1",
+ description: "The desired curve of the generated key (if key_type is equal to `ec`). Run `openssl ecparam -list_curves` to see available options."
+
+ action :create do
+ description "Generate a certificate request."
+
+ unless ::File.exist? new_resource.path
+ converge_by("Create CSR #{@new_resource}") do
+ file new_resource.path do
+ owner new_resource.owner unless new_resource.owner.nil?
+ group new_resource.group unless new_resource.group.nil?
+ mode new_resource.mode unless new_resource.mode.nil?
+ content csr.to_pem
+ action :create
+ end
+
+ file key_file do
+ owner new_resource.owner unless new_resource.owner.nil?
+ group new_resource.group unless new_resource.group.nil?
+ mode new_resource.mode unless new_resource.mode.nil?
+ content key.to_pem
+ sensitive true
+ action :create_if_missing
+ end
+ end
+ end
+ end
+
+ action_class do
+ def key_file
+ @key_file ||=
+ if new_resource.key_file
+ new_resource.key_file
+ else
+ path, file = ::File.split(new_resource.path)
+ filename = ::File.basename(file, ::File.extname(file))
+ path + "/" + filename + ".key"
+ end
+ end
+
+ def key
+ @key ||= if priv_key_file_valid?(key_file, new_resource.key_pass)
+ OpenSSL::PKey.read ::File.read(key_file), new_resource.key_pass
+ elsif new_resource.key_type == "rsa"
+ gen_rsa_priv_key(new_resource.key_length)
+ else
+ gen_ec_priv_key(new_resource.key_curve)
+ end
+ end
+
+ def subject
+ OpenSSL::X509::Name.new.tap do |csr_subject|
+ csr_subject.add_entry("C", new_resource.country) unless new_resource.country.nil?
+ csr_subject.add_entry("ST", new_resource.state) unless new_resource.state.nil?
+ csr_subject.add_entry("L", new_resource.city) unless new_resource.city.nil?
+ csr_subject.add_entry("O", new_resource.org) unless new_resource.org.nil?
+ csr_subject.add_entry("OU", new_resource.org_unit) unless new_resource.org_unit.nil?
+ csr_subject.add_entry("CN", new_resource.common_name)
+ csr_subject.add_entry("emailAddress", new_resource.email) unless new_resource.email.nil?
+ end
+ end
+
+ def csr
+ gen_x509_request(subject, key)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/osx_profile.rb b/lib/chef/resource/osx_profile.rb
index 8142e1fd96..491f30be43 100644
--- a/lib/chef/resource/osx_profile.rb
+++ b/lib/chef/resource/osx_profile.rb
@@ -16,59 +16,320 @@
# limitations under the License.
#
-require "chef/resource"
+require_relative "../resource"
+require_relative "../log"
+require_relative "../resource/file"
+autoload :UUIDTools, "uuidtools"
+autoload :Plist, "plist"
class Chef
class Resource
class OsxProfile < Chef::Resource
- provides :osx_profile, os: "darwin"
- provides :osx_config_profile, os: "darwin"
+ unified_mode true
- identity_attr :profile_name
+ provides :osx_profile
+ provides :osx_config_profile
- default_action :install
- allowed_actions :install, :remove
+ description "Use the **osx_profile** resource to manage configuration profiles (`.mobileconfig` files) on the macOS platform. The **osx_profile** resource installs profiles by using the uuidgen library to generate a unique `ProfileUUID`, and then using the `profiles` command to install the profile on the system."
+ introduced "12.7"
+ examples <<~DOC
+ **Install a profile from a cookbook file**
- def initialize(name, run_context = nil)
- super
- @profile_name = name
- @profile = nil
- @identifier = nil
- @path = nil
+ ```ruby
+ osx_profile 'com.company.screensaver.mobileconfig'
+ ```
+
+ **Install profile from a hash**
+
+ ```ruby
+ profile_hash = {
+ 'PayloadIdentifier' => 'com.company.screensaver',
+ 'PayloadRemovalDisallowed' => false,
+ 'PayloadScope' => 'System',
+ 'PayloadType' => 'Configuration',
+ 'PayloadUUID' => '1781fbec-3325-565f-9022-8aa28135c3cc',
+ 'PayloadOrganization' => 'Chef',
+ 'PayloadVersion' => 1,
+ 'PayloadDisplayName' => 'Screensaver Settings',
+ 'PayloadContent'=> [
+ {
+ 'PayloadType' => 'com.apple.ManagedClient.preferences',
+ 'PayloadVersion' => 1,
+ 'PayloadIdentifier' => 'com.company.screensaver',
+ 'PayloadUUID' => '73fc30e0-1e57-0131-c32d-000c2944c108',
+ 'PayloadEnabled' => true,
+ 'PayloadDisplayName' => 'com.apple.screensaver',
+ 'PayloadContent' => {
+ 'com.apple.screensaver' => {
+ 'Forced' => [
+ {
+ 'mcx_preference_settings' => {
+ 'idleTime' => 0,
+ },
+ },
+ ],
+ },
+ },
+ },
+ ],
+ }
+
+ osx_profile 'Install screensaver profile' do
+ profile profile_hash
+ end
+ ```
+
+ **Remove profile using identifier in resource name**
+
+ ```ruby
+ osx_profile 'com.company.screensaver' do
+ action :remove
end
+ ```
+
+ **Remove profile by identifier and user friendly resource name**
+
+ ```ruby
+ osx_profile 'Remove screensaver profile' do
+ identifier 'com.company.screensaver'
+ action :remove
+ end
+ ```
+ DOC
+
+ property :profile_name, String,
+ description: "Use to specify the name of the profile, if different from the name of the resource block.",
+ name_property: true
+
+ property :profile, [ String, Hash ],
+ description: "Use to specify a profile. This may be the name of a profile contained in a cookbook or a Hash that contains the contents of the profile."
- def profile_name(arg = nil)
- set_or_return(
- :profile_name,
- arg,
- :kind_of => [ String ]
- )
+ property :identifier, String,
+ description: "Use to specify the identifier for the profile, such as `com.company.screensaver`."
+
+ # this is not a property it is necessary for the tempfile this resource uses to work (FIXME: this is terrible)
+ #
+ # @api private
+ #
+ def path(path = nil)
+ @path ||= path
+ @path
end
- def profile(arg = nil)
- set_or_return(
- :profile,
- arg,
- :kind_of => [ String, Hash ]
- )
+ action_class do
+ def load_current_resource
+ @current_resource = Chef::Resource::OsxProfile.new(new_resource.name)
+ current_resource.profile_name(new_resource.profile_name)
+
+ if new_profile_hash
+ new_profile_hash["PayloadUUID"] = config_uuid(new_profile_hash)
+ end
+
+ current_resource.profile(current_profile)
+ end
+
+ def current_profile
+ all_profiles = get_installed_profiles
+
+ if all_profiles && all_profiles.key?("_computerlevel")
+ return all_profiles["_computerlevel"].find do |item|
+ item["ProfileIdentifier"] == new_profile_identifier
+ end
+ end
+ nil
+ end
+
+ def invalid_profile_name?(name_or_identifier)
+ name_or_identifier.end_with?(".mobileconfig") || !/^\w+(?:(\.| )\w+)+$/.match(name_or_identifier)
+ end
+
+ def check_resource_semantics!
+ if action == :remove
+ if new_profile_identifier
+ if invalid_profile_name?(new_profile_identifier)
+ raise "when removing using the identifier property, it must match the profile identifier"
+ end
+ else
+ if invalid_profile_name?(new_resource.profile_name)
+ raise "When removing by resource name, it must match the profile identifier"
+ end
+ end
+ end
+
+ if action == :install
+ # we only do this check for the install action so that profiles can still be removed on macOS 11+
+ if mac? && node["platform_version"] =~ ">= 11.0"
+ raise "The osx_profile resource is not available on macOS Big Sur or above due to Apple's removal of support for CLI profile installation"
+ end
+
+ if new_profile_hash.is_a?(Hash) && !new_profile_hash.include?("PayloadIdentifier")
+ raise "The specified profile does not seem to be valid"
+ end
+ if new_profile_hash.is_a?(String) && !new_profile_hash.end_with?(".mobileconfig")
+ raise "#{new_profile_hash}' is not a valid profile"
+ end
+ end
+ end
end
- def identifier(arg = nil)
- set_or_return(
- :identifier,
- arg,
- :kind_of => [ String ]
- )
+ action :install do
+ 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 path(arg = nil)
- set_or_return(
- :path,
- arg,
- :kind_of => [ String ]
- )
+ action :remove do
+ # 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
+ action_class do
+ private
+
+ def profile
+ @profile ||= new_resource.profile || new_resource.profile_name
+ end
+
+ def new_profile_hash
+ @new_profile_hash ||= get_profile_hash(profile)
+ end
+
+ def new_profile_identifier
+ @new_profile_identifier ||= if new_profile_hash
+ new_profile_hash["PayloadIdentifier"]
+ else
+ new_resource.identifier || new_resource.profile_name
+ end
+ end
+
+ def load_profile_hash(new_profile)
+ # file must exist in cookbook
+ return nil unless new_profile.end_with?(".mobileconfig")
+
+ unless cookbook_file_available?(new_profile)
+ raise Chef::Exceptions::FileNotFound, "#{self}: '#{new_profile}' not found in cookbook"
+ end
+
+ cookbook_profile = cache_cookbook_profile(new_profile)
+ ::Plist.parse_xml(cookbook_profile)
+ 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
+ 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)
+ )
+ )
+
+ path = ::File.join( get_cache_dir, "#{cookbook_file}.remote")
+
+ cookbook_file path do
+ cookbook_name = new_resource.cookbook_name
+ source(cookbook_file)
+ backup(false)
+ run_action(:create)
+ end
+
+ path
+ end
+
+ def get_profile_hash(new_profile)
+ if new_profile.is_a?(Hash)
+ new_profile
+ elsif new_profile.is_a?(String)
+ 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
+ # FIXME: this is kind of terrible, the resource needs a tempfile to use and
+ # wants it created similarly to the file providers (with all the magic necessary
+ # for determining if it should go in the cwd or into a tmpdir), but it abuses
+ # the Chef::FileContentManagement::Tempfile API to do that, which requires setting
+ # a `path` method on the resource because of tight-coupling to the file provider
+ # pattern. We don't just want to use a file here because the point is to get
+ # at the tempfile pattern from the file provider, but to feed that into a shell
+ # command rather than deploying the file to somewhere on disk. There's some
+ # better API that needs extracting here.
+ 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 = [ "/usr/bin/profiles", "-I", "-F", profile_path ]
+ logger.trace("cmd: #{cmd.join(" ")}")
+ shell_out!(*cmd)
+ end
+
+ def remove_profile
+ cmd = [ "/usr/bin/profiles", "-R", "-p", new_profile_identifier ]
+ logger.trace("cmd: #{cmd.join(" ")}")
+ shell_out!(*cmd)
+ end
+
+ #
+ # FIXME FIXME FIXME
+ # The node object should not be used for caching state like this and this is not a public API and may break.
+ # FIXME FIXME FIXME
+ #
+
+ def get_installed_profiles(update = nil)
+ logger.trace("Saving profile data to node.run_state")
+ 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
+ logger.trace("Running /usr/bin/profiles -P -o stdout-xml to determine profile state")
+ so = shell_out( "/usr/bin/profiles", "-P", "-o", "stdout-xml" )
+ ::Plist.parse_xml(so.stdout)
+ end
+
+ def profile_installed?
+ # Profile Identifier and UUID must match a currently installed profile
+ return false if current_resource.profile.nil? || current_resource.profile.empty?
+ return true if action == :remove
+
+ current_resource.profile["ProfileUUID"] == new_profile_hash["PayloadUUID"]
+ end
+ end
end
end
end
diff --git a/lib/chef/resource/package.rb b/lib/chef/resource/package.rb
index 32339e1a24..db3b12d289 100644
--- a/lib/chef/resource/package.rb
+++ b/lib/chef/resource/package.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,15 +17,24 @@
# limitations under the License.
#
-require "chef/resource"
+require_relative "../resource"
class Chef
class Resource
class Package < Chef::Resource
- resource_name :package
+ unified_mode true
+ provides :package
+
+ description "Use the **package** resource to manage packages. When the package is"\
+ " installed from a local file (such as with RubyGems, dpkg, or RPM"\
+ " Package Manager), the file must be added to the node using the remote_file"\
+ " or cookbook_file resources.\n\nThis resource is the base resource for"\
+ " several other resources used for package management on specific platforms."\
+ " While it is possible to use each of these specific resources, it is"\
+ " recommended to use the package resource as often as possible."
default_action :install
- allowed_actions :install, :upgrade, :remove, :purge, :reconfig
+ allowed_actions :install, :upgrade, :remove, :purge, :reconfig, :lock, :unlock
def initialize(name, *args)
# We capture name here, before it gets coerced to name
@@ -33,14 +42,24 @@ class Chef
super
end
- property :package_name, [ String, Array ], identity: true
+ property :package_name, [ String, Array ],
+ description: "An optional property to set the package name if it differs from the resource block's name.",
+ identity: true
+
+ property :version, [ String, Array ],
+ description: "The version of a package to be installed or upgraded."
+
+ property :options, [ String, Array ],
+ description: "One (or more) additional command options that are passed to the command.",
+ coerce: proc { |x| x.is_a?(String) ? x.shellsplit : x }
+
+ property :source, String,
+ description: "The optional path to a package on the local file system.",
+ desired_state: false
- 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
+ property :timeout, [ String, Integer ],
+ description: "The amount of time (in seconds) to wait before timing out.",
+ desired_state: false
end
end
diff --git a/lib/chef/resource/pacman_package.rb b/lib/chef/resource/pacman_package.rb
index 66b39d164d..14856de7dd 100644
--- a/lib/chef/resource/pacman_package.rb
+++ b/lib/chef/resource/pacman_package.rb
@@ -16,13 +16,16 @@
# limitations under the License.
#
-require "chef/resource/package"
+require_relative "package"
class Chef
class Resource
class PacmanPackage < Chef::Resource::Package
- resource_name :pacman_package
- provides :pacman_package, os: "linux"
+ unified_mode true
+
+ provides :pacman_package
+
+ description "Use the **pacman_package** resource to manage packages (using pacman) on the Arch Linux platform."
end
end
end
diff --git a/lib/chef/resource/paludis_package.rb b/lib/chef/resource/paludis_package.rb
index 31c0f31b8c..385922c940 100644
--- a/lib/chef/resource/paludis_package.rb
+++ b/lib/chef/resource/paludis_package.rb
@@ -1,6 +1,6 @@
#
# Author:: Vasiliy Tolstov (<v.tolstov@selfip.ru>)
-# Copyright:: Copyright 2014-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,18 +16,32 @@
# limitations under the License.
#
-require "chef/resource/package"
-require "chef/provider/package/paludis"
+require_relative "package"
+require_relative "../provider/package/paludis"
class Chef
class Resource
class PaludisPackage < Chef::Resource::Package
- resource_name :paludis_package
- provides :paludis_package, os: "linux"
+ unified_mode true
+
+ provides :paludis_package
+
+ description "Use the **paludis_package** resource to manage packages for the Paludis platform."
+ introduced "12.1"
allowed_actions :install, :remove, :upgrade
- property :timeout, default: 3600
+ property :package_name, String,
+ description: "An optional property to set the package name if it differs from the resource block's name.",
+ identity: true
+
+ property :version, String,
+ description: "The version of a package to be installed or upgraded."
+
+ property :timeout, [String, Integer],
+ description: "The amount of time (in seconds) to wait before timing out.",
+ default: 3600,
+ desired_state: false
end
end
end
diff --git a/lib/chef/resource/perl.rb b/lib/chef/resource/perl.rb
index 60af0e92da..ac98c69a8c 100644
--- a/lib/chef/resource/perl.rb
+++ b/lib/chef/resource/perl.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,17 +16,26 @@
# limitations under the License.
#
-require "chef/resource/script"
-require "chef/provider/script"
+require_relative "script"
class Chef
class Resource
class Perl < Chef::Resource::Script
+ unified_mode true
+
+ provides :perl
+
def initialize(name, run_context = nil)
super
@interpreter = "perl"
end
+ description "Use the **perl** resource to execute scripts using the Perl interpreter."\
+ " This resource may also use any of the actions and properties that are"\
+ " available to the **execute** resource. Commands that are executed with this"\
+ " resource are (by their nature) not idempotent, as they are typically"\
+ " unique to the environment in which they are run. Use `not_if` and `only_if`"\
+ " to guard this resource for idempotence."
end
end
end
diff --git a/lib/chef/resource/plist.rb b/lib/chef/resource/plist.rb
new file mode 100644
index 0000000000..a7cb88ef57
--- /dev/null
+++ b/lib/chef/resource/plist.rb
@@ -0,0 +1,222 @@
+#
+# Copyright:: Copyright 2017-2020, Microsoft Corporation
+# Copyright:: Copyright (c) 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_relative "../resource"
+autoload :Plist, "plist"
+
+class Chef
+ class Resource
+
+ class PlistResource < Chef::Resource # we name this PlistResource to avoid confusion with Plist from the plist gem
+ unified_mode true
+
+ provides :plist
+
+ description "Use the **plist** resource to set config values in plist files on macOS systems."
+ introduced "16.0"
+ examples <<~DOC
+ **Show hidden files in finder**:
+
+ ```ruby
+ plist 'show hidden files' do
+ path '/Users/vagrant/Library/Preferences/com.apple.finder.plist'
+ entry 'AppleShowAllFiles'
+ value true
+ end
+ ```
+ DOC
+
+ property :path, String, name_property: true,
+ description: "The path on disk to the plist file."
+
+ property :entry, String
+ property :value, [TrueClass, FalseClass, String, Integer, Float, Hash]
+ property :encoding, String, default: "binary"
+
+ property :owner, String, default: "root",
+ description: "The owner of the plist file."
+
+ property :group, String, default: "wheel",
+ description: "The group of the plist file."
+
+ property :mode, [String, Integer],
+ description: "The file mode of the plist file. Ex: '644'"
+
+ PLISTBUDDY_EXECUTABLE = "/usr/libexec/PlistBuddy".freeze
+ DEFAULTS_EXECUTABLE = "/usr/bin/defaults".freeze
+ PLUTIL_EXECUTABLE = "/usr/bin/plutil".freeze
+ PLUTIL_FORMAT_MAP = { "us-ascii" => "xml1",
+ "text/xml" => "xml1",
+ "utf-8" => "xml1",
+ "binary" => "binary1" }.freeze
+
+ load_current_value do |desired|
+ current_value_does_not_exist! unless ::File.exist? desired.path
+ entry desired.entry if entry_in_plist? desired.entry, desired.path
+
+ setting = setting_from_plist desired.entry, desired.path
+ value convert_to_data_type_from_string(setting[:key_type], setting[:key_value])
+
+ file_type_cmd = shell_out "/usr/bin/file", "--brief", "--mime-encoding", "--preserve-date", desired.path
+ encoding file_type_cmd.stdout.chomp
+
+ file_owner_cmd = shell_out("/usr/bin/stat", "-f", "%Su", desired.path)
+ owner file_owner_cmd.stdout.chomp
+
+ file_group_cmd = shell_out("/usr/bin/stat", "-f", "%Sg", desired.path)
+ group file_group_cmd.stdout.chomp
+ end
+
+ action :set do
+ converge_if_changed :path do
+ converge_by "create new plist: '#{new_resource.path}'" do
+ file new_resource.path do
+ content {}.to_plist
+ owner new_resource.owner
+ group new_resource.group
+ mode new_resource.mode if property_is_set?(:mode)
+ end
+ end
+ end
+
+ plist_file_name = ::File.basename(new_resource.path)
+
+ converge_if_changed :entry do
+ converge_by "add entry \"#{new_resource.entry}\" to #{plist_file_name}" do
+ shell_out!(plistbuddy_command(:add, new_resource.entry, new_resource.path, new_resource.value))
+ end
+ end
+
+ converge_if_changed :value do
+ converge_by "#{plist_file_name}: set #{new_resource.entry} to #{new_resource.value}" do
+ shell_out!(plistbuddy_command(:set, new_resource.entry, new_resource.path, new_resource.value))
+ end
+ end
+
+ converge_if_changed :encoding do
+ converge_by "change format" do
+ unless PLUTIL_FORMAT_MAP.key?(new_resource.encoding)
+ Chef::Application.fatal!(
+ "Option encoding must be equal to one of: #{PLUTIL_FORMAT_MAP.keys}! You passed \"#{new_resource.encoding}\"."
+ )
+ end
+ shell_out!(PLUTIL_EXECUTABLE, "-convert", PLUTIL_FORMAT_MAP[new_resource.encoding], new_resource.path)
+ end
+ end
+
+ converge_if_changed :owner do
+ converge_by "update owner to #{new_resource.owner}" do
+ file new_resource.path do
+ owner new_resource.owner
+ end
+ end
+ end
+
+ converge_if_changed :group do
+ converge_by "update group to #{new_resource.group}" do
+ file new_resource.path do
+ group new_resource.group
+ end
+ end
+ end
+ end
+
+ ### Question: Should I refactor these methods into an action_class?
+ ### Answer: NO
+ ### Why: We need them in both the action and in load_current_value. If you put them in the
+ ### action class then they're only in the Provider class and are not available to load_current_value
+
+ def convert_to_data_type_from_string(type, value)
+ case type
+ when "boolean"
+ # Since we've determined this is a boolean data type, we can assume that:
+ # If the value as an int is 1, return true
+ # If the value as an int is 0 (not 1), return false
+ value.to_i == 1
+ when "integer"
+ value.to_i
+ when "float"
+ value.to_f
+ when "string", "dictionary"
+ value
+ when nil
+ ""
+ else
+ raise "Unknown or unsupported data type: #{type.class}"
+ end
+ end
+
+ def type_to_commandline_string(value)
+ case value
+ when Array
+ "array"
+ when Integer
+ "integer"
+ when FalseClass, TrueClass
+ "bool"
+ when Hash
+ "dict"
+ when String
+ "string"
+ when Float
+ "float"
+ else
+ raise "Unknown or unsupported data type: #{value} of #{value.class}"
+ end
+ end
+
+ def entry_in_plist?(entry, path)
+ print_entry = plistbuddy_command :print, entry, path
+ cmd = shell_out print_entry
+ cmd.exitstatus == 0
+ end
+
+ def plistbuddy_command(subcommand, entry, path, value = nil)
+ sep = " "
+ arg = case subcommand.to_s
+ when "add"
+ type_to_commandline_string(value)
+ when "set"
+ if value.is_a?(Hash)
+ sep = ":"
+ value.map { |k, v| "#{k} #{v}" }
+ else
+ value
+ end
+ else
+ ""
+ end
+ entry_with_arg = ["\"#{entry}\"", arg].join(sep).strip
+ subcommand = "#{subcommand.capitalize} :#{entry_with_arg}"
+ [PLISTBUDDY_EXECUTABLE, "-c", "\'#{subcommand}\'", "\"#{path}\""].join(" ")
+ end
+
+ def setting_from_plist(entry, path)
+ defaults_read_type_output = shell_out(DEFAULTS_EXECUTABLE, "read-type", path, entry).stdout
+ data_type = defaults_read_type_output.split.last
+
+ if value.class == Hash
+ plutil_output = shell_out(PLUTIL_EXECUTABLE, "-extract", entry, "xml1", "-o", "-", path).stdout.chomp
+ { key_type: data_type, key_value: ::Plist.parse_xml(plutil_output) }
+ else
+ defaults_read_output = shell_out(DEFAULTS_EXECUTABLE, "read", path, entry).stdout
+ { key_type: data_type, key_value: defaults_read_output.strip }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/portage_package.rb b/lib/chef/resource/portage_package.rb
index ad66c7b42b..05f54c9d21 100644
--- a/lib/chef/resource/portage_package.rb
+++ b/lib/chef/resource/portage_package.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,17 +16,28 @@
# limitations under the License.
#
-require "chef/resource/package"
+require_relative "package"
class Chef
class Resource
class PortagePackage < Chef::Resource::Package
- resource_name :portage_package
- def initialize(name, run_context = nil)
- super
- @provider = Chef::Provider::Package::Portage
- end
+ unified_mode true
+ provides :portage_package
+
+ description "Use the **portage_package** resource to manage packages for the Gentoo platform."
+
+ property :package_name, String,
+ description: "An optional property to set the package name if it differs from the resource block's name.",
+ identity: true
+
+ property :version, String,
+ description: "The version of a package to be installed or upgraded."
+
+ property :timeout, [String, Integer],
+ default: 3600,
+ description: "The amount of time (in seconds) to wait before timing out.",
+ desired_state: false
end
end
end
diff --git a/lib/chef/resource/powershell_package.rb b/lib/chef/resource/powershell_package.rb
new file mode 100644
index 0000000000..7d013eac4c
--- /dev/null
+++ b/lib/chef/resource/powershell_package.rb
@@ -0,0 +1,50 @@
+# Author:: Dheeraj Dubey(dheeraj.dubey@msystechnologies.com)
+# Copyright:: Copyright (c) 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_relative "package"
+
+class Chef
+ class Resource
+ class PowershellPackage < Chef::Resource::Package
+ unified_mode true
+
+ provides :powershell_package
+
+ description "Use the **powershell_package** resource to install and manage packages via the PowerShell Package Manager for the Microsoft Windows platform. The powershell_package resource requires administrative access, and a source must be configured in the PowerShell Package Manager via the powershell_package_source resource."
+ introduced "12.16"
+
+ allowed_actions :install, :remove
+
+ property :package_name, [String, Array],
+ description: "The name of the package. Default value: the name of the resource block.",
+ coerce: proc { |x| [x].flatten }
+
+ property :version, [String, Array],
+ description: "The version of a package to be installed or upgraded.",
+ coerce: proc { |x| [x].flatten }
+
+ property :source, String,
+ description: "Specify the source of the package.",
+ introduced: "14.0"
+
+ property :skip_publisher_check, [true, false],
+ description: "Skip validating module author.",
+ default: false, introduced: "14.3", desired_state: false
+
+ end
+ end
+end
diff --git a/lib/chef/resource/powershell_package_source.rb b/lib/chef/resource/powershell_package_source.rb
new file mode 100644
index 0000000000..066efc6a72
--- /dev/null
+++ b/lib/chef/resource/powershell_package_source.rb
@@ -0,0 +1,171 @@
+# Author:: Tor Magnus Rakvåg (tm@intility.no)
+# Copyright:: 2018, Intility AS
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../resource"
+
+class Chef
+ class Resource
+ class PowershellPackageSource < Chef::Resource
+ unified_mode true
+
+ provides :powershell_package_source
+
+ description "Use the **powershell_package_source** resource to register a PowerShell package repository."
+ introduced "14.3"
+
+ property :source_name, String,
+ description: "The name of the package source.",
+ name_property: true
+
+ property :url, String,
+ description: "The URL to the package source.",
+ required: [:register]
+
+ property :trusted, [TrueClass, FalseClass],
+ description: "Whether or not to trust packages from this source.",
+ default: false
+
+ property :provider_name, String,
+ equal_to: %w{ Programs msi NuGet msu PowerShellGet psl chocolatey },
+ validation_message: "The following providers are supported: 'Programs', 'msi', 'NuGet', 'msu', 'PowerShellGet', 'psl' or 'chocolatey'",
+ description: "The package management provider for the source.",
+ default: "NuGet"
+
+ property :publish_location, String,
+ description: "The URL where modules will be published to for this source. Only valid if the provider is `PowerShellGet`."
+
+ property :script_source_location, String,
+ description: "The URL where scripts are located for this source. Only valid if the provider is `PowerShellGet`."
+
+ property :script_publish_location, String,
+ description: "The location where scripts will be published to for this source. Only valid if the provider is `PowerShellGet`."
+
+ load_current_value do
+ cmd = load_resource_state_script(source_name)
+ repo = powershell_exec!(cmd)
+ if repo.result.empty?
+ current_value_does_not_exist!
+ else
+ status = repo.result
+ end
+ url status["url"]
+ trusted status["trusted"]
+ provider_name status["provider_name"]
+ publish_location status["publish_location"]
+ script_source_location status["script_source_location"]
+ script_publish_location status["script_publish_location"]
+ end
+
+ action :register do
+ description "Registers and updates the powershell package source."
+ # TODO: Ensure package provider is installed?
+ if psrepository_cmdlet_appropriate?
+ if package_source_exists?
+ converge_if_changed :url, :trusted, :publish_location, :script_source_location, :script_publish_location do
+ update_cmd = build_ps_repository_command("Set", new_resource)
+ res = powershell_exec(update_cmd)
+ raise "Failed to update #{new_resource.source_name}: #{res.errors}" if res.error?
+ end
+ else
+ converge_by("register source: #{new_resource.source_name}") do
+ register_cmd = build_ps_repository_command("Register", new_resource)
+ res = powershell_exec(register_cmd)
+ raise "Failed to register #{new_resource.source_name}: #{res.errors}" if res.error?
+ end
+ end
+ else
+ if package_source_exists?
+ converge_if_changed :url, :trusted, :provider_name do
+ update_cmd = build_package_source_command("Set", new_resource)
+ res = powershell_exec(update_cmd)
+ raise "Failed to update #{new_resource.source_name}: #{res.errors}" if res.error?
+ end
+ else
+ converge_by("register source: #{new_resource.source_name}") do
+ register_cmd = build_package_source_command("Register", new_resource)
+ res = powershell_exec(register_cmd)
+ raise "Failed to register #{new_resource.source_name}: #{res.errors}" if res.error?
+ end
+ end
+ end
+ end
+
+ action :unregister do
+ description "Unregisters the powershell package source."
+ if package_source_exists?
+ unregister_cmd = "Get-PackageSource -Name '#{new_resource.source_name}' | Unregister-PackageSource"
+ converge_by("unregister source: #{new_resource.source_name}") do
+ res = powershell_exec(unregister_cmd)
+ raise "Failed to unregister #{new_resource.source_name}: #{res.errors}" if res.error?
+ end
+ end
+ end
+
+ action_class do
+ def package_source_exists?
+ cmd = powershell_exec!("(Get-PackageSource -Name '#{new_resource.source_name}' -ErrorAction SilentlyContinue).Name")
+ !cmd.result.empty? && cmd.result.to_s.downcase.strip == new_resource.source_name.downcase
+ end
+
+ def psrepository_cmdlet_appropriate?
+ new_resource.provider_name == "PowerShellGet"
+ end
+
+ def build_ps_repository_command(cmdlet_type, new_resource)
+ cmd = "#{cmdlet_type}-PSRepository -Name '#{new_resource.source_name}'"
+ cmd << " -SourceLocation '#{new_resource.url}'" if new_resource.url
+ cmd << " -InstallationPolicy '#{new_resource.trusted ? "Trusted" : "Untrusted"}'"
+ cmd << " -PublishLocation '#{new_resource.publish_location}'" if new_resource.publish_location
+ cmd << " -ScriptSourceLocation '#{new_resource.script_source_location}'" if new_resource.script_source_location
+ cmd << " -ScriptPublishLocation '#{new_resource.script_publish_location}'" if new_resource.script_publish_location
+ cmd << " | Out-Null"
+ cmd
+ end
+
+ def build_package_source_command(cmdlet_type, new_resource)
+ cmd = "#{cmdlet_type}-PackageSource -Name '#{new_resource.source_name}'"
+ cmd << " -Location '#{new_resource.url}'" if new_resource.url
+ cmd << " -Trusted:#{new_resource.trusted ? "$true" : "$false"}"
+ cmd << " -ProviderName '#{new_resource.provider_name}'" if new_resource.provider_name
+ cmd << " | Out-Null"
+ cmd
+ end
+ end
+ end
+
+ private
+
+ def load_resource_state_script(name)
+ <<-EOH
+ $PSDefaultParameterValues = @{
+ "*:WarningAction" = "SilentlyContinue"
+ }
+ if(Get-PackageSource -Name '#{name}' -ErrorAction SilentlyContinue) {
+ if ((Get-PackageSource -Name '#{name}').ProviderName -eq 'PowerShellGet') {
+ (Get-PSRepository -Name '#{name}') | Select @{n='source_name';e={$_.Name}}, @{n='url';e={$_.SourceLocation}},
+ @{n='trusted';e={$_.Trusted}}, @{n='provider_name';e={$_.PackageManagementProvider}}, @{n='publish_location';e={$_.PublishLocation}},
+ @{n='script_source_location';e={$_.ScriptSourceLocation}}, @{n='script_publish_location';e={$_.ScriptPublishLocation}}
+ }
+ else {
+ (Get-PackageSource -Name '#{name}') | Select @{n='source_name';e={$_.Name}}, @{n='url';e={$_.Location}},
+ @{n='provider_name';e={$_.ProviderName}}, @{n='trusted';e={$_.IsTrusted}}
+ }
+ }
+ EOH
+ end
+ end
+end
diff --git a/lib/chef/resource/powershell_script.rb b/lib/chef/resource/powershell_script.rb
index a530a9116c..eb72518009 100644
--- a/lib/chef/resource/powershell_script.rb
+++ b/lib/chef/resource/powershell_script.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Edwards (<adamed@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,24 +15,58 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-require "chef/resource/windows_script"
+require_relative "windows_script"
class Chef
class Resource
class PowershellScript < Chef::Resource::WindowsScript
+ unified_mode true
+
+ set_guard_inherited_attributes(:interpreter)
+
provides :powershell_script, os: "windows"
- def initialize(name, run_context = nil)
- super(name, run_context, nil, "powershell.exe")
- @convert_boolean_return = false
- end
+ description <<~DESC
+ Use the **powershell_script** resource to execute a script using the Windows PowerShell interpreter, much like how the script and script-based resources **bash**, **csh**, **perl**, **python**, and **ruby** are used. The **powershell_script** resource is specific to the Microsoft Windows platform, but may use both the the Windows PowerShell interpreter or the PowerShell Core (pwsh) interpreter as of Chef Infra Client 16.6 and later.
+
+ The **powershell_script** resource creates and executes a temporary file rather than running the command inline. Commands that are executed with this resource are (by their nature) not idempotent, as they are typically unique to the environment in which they are run. Use `not_if` and `only_if` conditionals to guard this resource for idempotence.
+ DESC
+
+ property :flags, String,
+ description: "A string that is passed to the Windows PowerShell command"
+
+ property :interpreter, String,
+ default: "powershell",
+ equal_to: %w{powershell pwsh},
+ description: "The interpreter type, `powershell` or `pwsh` (PowerShell Core)"
+
+ property :convert_boolean_return, [true, false],
+ default: false,
+ description: <<~DESC
+ Return `0` if the last line of a command is evaluated to be true or to return `1` if the last line is evaluated to be false.
+
+ When the `guard_interpreter` common attribute is set to `:powershell_script`, a string command will be evaluated as if this value were set to `true`. This is because the behavior of this attribute is similar to the value of the `"$?"` expression common in UNIX interpreters. For example, this:
+
+ ```ruby
+ powershell_script 'make_safe_backup' do
+ guard_interpreter :powershell_script
+ code 'cp ~/data/nodes.json ~/data/nodes.bak'
+ not_if 'test-path ~/data/nodes.bak'
+ end
+ ```
+
+ is similar to:
+ ```ruby
+ bash 'make_safe_backup' do
+ code 'cp ~/data/nodes.json ~/data/nodes.bak'
+ not_if 'test -e ~/data/nodes.bak'
+ end
+ ```
+ DESC
- def convert_boolean_return(arg = nil)
- set_or_return(
- :convert_boolean_return,
- arg,
- :kind_of => [ FalseClass, TrueClass ]
- )
+ def initialize(*args)
+ super
+ @default_guard_interpreter = resource_name
end
# Allow callers evaluating guards to request default
@@ -42,8 +76,8 @@ class Chef
# default for this resource, this method can be removed since
# guard context and recipe resource context will have the
# same behavior.
- def self.get_default_attributes(opts)
- { :convert_boolean_return => true }
+ def self.get_default_attributes
+ { convert_boolean_return: true }
end
end
end
diff --git a/lib/chef/resource/python.rb b/lib/chef/resource/python.rb
index bcad3d090b..0b36ca0bbd 100644
--- a/lib/chef/resource/python.rb
+++ b/lib/chef/resource/python.rb
@@ -1,5 +1,5 @@
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,17 +15,25 @@
# limitations under the License.
#
-require "chef/resource/script"
-require "chef/provider/script"
+require_relative "script"
class Chef
class Resource
class Python < Chef::Resource::Script
+ unified_mode true
+
+ provides :python
+
def initialize(name, run_context = nil)
super
@interpreter = "python"
end
+ description "Use the **python** resource to execute scripts using the Python interpreter."\
+ " This resource may also use any of the actions and properties that are available"\
+ " to the **execute** resource. Commands that are executed with this resource are (by"\
+ " their nature) not idempotent, as they are typically unique to the environment in"\
+ " which they are run. Use `not_if` and `only_if` to guard this resource for idempotence."
end
end
end
diff --git a/lib/chef/resource/reboot.rb b/lib/chef/resource/reboot.rb
index 24d6e74157..6ac19e299b 100644
--- a/lib/chef/resource/reboot.rb
+++ b/lib/chef/resource/reboot.rb
@@ -1,6 +1,6 @@
#
# Author:: Chris Doherty <cdoherty@chef.io>)
-# Copyright:: Copyright 2014-2016, Chef, Inc.
+# Copyright:: Copyright 2014-2019, Chef, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,32 +16,76 @@
# limitations under the License.
#
-require "chef/resource"
+require_relative "../resource"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
-# In using this resource via notifications, it's important to *only* use
-# immediate notifications. Delayed notifications produce unintuitive and
-# probably undesired results.
class Chef
class Resource
class Reboot < Chef::Resource
- allowed_actions :request_reboot, :reboot_now, :cancel
+ unified_mode true
- def initialize(name, run_context = nil)
- super
- @provider = Chef::Provider::Reboot
+ provides :reboot
- @reason = "Reboot by Chef"
- @delay_mins = 0
+ description "Use the **reboot** resource to reboot a node, a necessary step with some"\
+ " installations on certain platforms. This resource is supported for use on"\
+ " the Microsoft Windows, macOS, and Linux platforms.\n"\
+ "In using this resource via notifications, it's important to *only* use"\
+ " immediate notifications. Delayed notifications produce unintuitive and"\
+ " probably undesired results."
+ introduced "12.0"
- # no default action.
+ property :reason, String,
+ description: "A string that describes the reboot action.",
+ default: "Reboot by #{ChefUtils::Dist::Infra::PRODUCT}"
+
+ property :delay_mins, Integer,
+ description: "The amount of time (in minutes) to delay a reboot request.",
+ default: 0
+
+ action :request_reboot do
+ description "Reboot a node at the end of a chef-client run."
+
+ converge_by("request a system reboot to occur if the run succeeds") do
+ logger.warn "Reboot requested:'#{new_resource.name}'"
+ request_reboot
+ end
+ end
+
+ action :reboot_now do
+ description "Reboot a node so that the chef-client may continue the installation process."
+
+ converge_by("rebooting the system immediately") do
+ logger.warn "Rebooting system immediately, requested by '#{new_resource.name}'"
+ request_reboot
+ throw :end_client_run_early
+ end
end
- def reason(arg = nil)
- set_or_return(:reason, arg, :kind_of => String)
+ action :cancel do
+ description "Cancel a pending reboot request."
+
+ converge_by("cancel any existing end-of-run reboot request") do
+ logger.warn "Reboot canceled: '#{new_resource.name}'"
+ node.run_context.cancel_reboot
+ end
end
- def delay_mins(arg = nil)
- set_or_return(:delay_mins, arg, :kind_of => Fixnum)
+ # make sure people are quite clear what they want
+ # we have to define this below the actions since setting default_action to :nothing is a no-op
+ # and doesn't actually override the first action in the resource
+ default_action :nothing
+
+ action_class do
+ # add a reboot to the node run_context
+ # @return [void]
+ def request_reboot
+ node.run_context.request_reboot(
+ delay_mins: new_resource.delay_mins,
+ reason: new_resource.reason,
+ timestamp: Time.now,
+ requested_by: new_resource.name
+ )
+ end
end
end
end
diff --git a/lib/chef/resource/registry_key.rb b/lib/chef/resource/registry_key.rb
index d11f826c38..6c17146fcb 100644
--- a/lib/chef/resource/registry_key.rb
+++ b/lib/chef/resource/registry_key.rb
@@ -1,7 +1,7 @@
# Author:: Prajakta Purohit (<prajakta@chef.io>)
# Author:: Lamont Granquist (<lamont@chef.io>)
#
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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.
@@ -15,14 +15,109 @@
# 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_relative "../resource"
+require_relative "../digester"
class Chef
class Resource
class RegistryKey < Chef::Resource
- identity_attr :key
+ unified_mode true
+
+ provides(:registry_key) { true }
+
+ description "Use the **registry_key** resource to create and delete registry keys in Microsoft Windows."
+ examples <<~'DOC'
+ **Create a registry key**
+
+ ```ruby
+ registry_key 'HKEY_LOCAL_MACHINE\\path-to-key\\Policies\\System' do
+ values [{
+ name: 'EnableLUA',
+ type: :dword,
+ data: 0
+ }]
+ action :create
+ end
+ ```
+
+ **Create a registry key with binary data: "\x01\x02\x03"**:
+
+ ```ruby
+ registry_key 'HKEY_CURRENT_USER\ChefTest' do
+ values [{
+ :name => "test",
+ :type => :binary,
+ :data => [0, 1, 2].map(&:chr).join
+ }]
+ action :create
+ end
+ ```
+
+ **Create 32-bit key in redirected wow6432 tree**
+
+ In 64-bit versions of Microsoft Windows, HKEY_LOCAL_MACHINE\SOFTWARE\Example is a re-directed key. In the following examples, because HKEY_LOCAL_MACHINE\SOFTWARE\Example is a 32-bit key, the output will be “Found 32-bit key” if they are run on a version of Microsoft Windows that is 64-bit:
+
+ ```ruby
+ registry_key 'HKEY_LOCAL_MACHINE\SOFTWARE\Example' do
+ architecture :i386
+ recursive true
+ action :create
+ end
+ ```
+
+ **Set proxy settings to be the same as those used by Chef Infra Client**
+
+ ```ruby
+ proxy = URI.parse(Chef::Config[:http_proxy])
+ registry_key 'HKCU\Software\Microsoft\path\to\key\Internet Settings' do
+ values [{name: 'ProxyEnable', type: :reg_dword, data: 1},
+ {name: 'ProxyServer', data: "#{proxy.host}:#{proxy.port}"},
+ {name: 'ProxyOverride', type: :reg_string, data: <local>},
+ ]
+ action :create
+ end
+ ```
+
+ **Set the name of a registry key to "(Default)"**
+
+ ```ruby
+ registry_key 'Set (Default) value' do
+ key 'HKLM\Software\Test\Key\Path'
+ values [
+ {name: '', type: :string, data: 'test'},
+ ]
+ action :create
+ end
+ ```
+
+ **Delete a registry key value**
+
+ ```ruby
+ registry_key 'HKEY_LOCAL_MACHINE\SOFTWARE\path\to\key\AU' do
+ values [{
+ name: 'NoAutoRebootWithLoggedOnUsers',
+ type: :dword,
+ data: ''
+ }]
+ action :delete
+ end
+ ```
+
+ Note: If data: is not specified, you get an error: Missing data key in RegistryKey values hash
+
+ **Delete a registry key and its subkeys, recursively**
+
+ ```ruby
+ registry_key 'HKCU\SOFTWARE\Policies\path\to\key\Themes' do
+ recursive true
+ action :delete_key
+ end
+ ```
+
+ Note: Be careful when using the :delete_key action with the recursive attribute. This will delete the registry key, all of its values and all of the names, types, and data associated with them. This cannot be undone by Chef Infra Client.
+ DOC
+
state_attrs :values
default_action :create
@@ -61,61 +156,46 @@ class Chef
def initialize(name, run_context = nil)
super
- @architecture = :machine
- @recursive = false
- @key = name
@values, @unscrubbed_values = [], []
end
- def key(arg = nil)
- set_or_return(
- :key,
- arg,
- :kind_of => String
- )
- end
+ property :key, String, name_property: true
+
+ VALID_VALUE_HASH_KEYS = %i{name type data}.freeze
def values(arg = nil)
if not arg.nil?
if arg.is_a?(Hash)
- @values = [ arg ]
+ @values = [ Mash.new(arg).symbolize_keys ]
elsif arg.is_a?(Array)
- @values = arg
+ @values = []
+ arg.each do |value|
+ @values << Mash.new(value).symbolize_keys
+ end
else
raise ArgumentError, "Bad type for RegistryKey resource, use Hash or Array"
end
@values.each do |v|
- raise ArgumentError, "Missing name key in RegistryKey values hash" unless v.has_key?(:name)
- raise ArgumentError, "Missing type key in RegistryKey values hash" unless v.has_key?(:type)
- raise ArgumentError, "Missing data key in RegistryKey values hash" unless v.has_key?(:data)
+ raise ArgumentError, "Missing name key in RegistryKey values hash" unless v.key?(:name)
+
v.each_key do |key|
- raise ArgumentError, "Bad key #{key} in RegistryKey values hash" unless [:name, :type, :data].include?(key)
+ raise ArgumentError, "Bad key #{key} in RegistryKey values hash" unless VALID_VALUE_HASH_KEYS.include?(key)
end
raise ArgumentError, "Type of name => #{v[:name]} should be string" unless v[:name].is_a?(String)
- raise ArgumentError, "Type of type => #{v[:type]} should be symbol" unless v[:type].is_a?(Symbol)
+
+ if v[:type]
+ raise ArgumentError, "Type of type => #{v[:type]} should be symbol" unless v[:type].is_a?(Symbol)
+ end
end
@unscrubbed_values = @values
- elsif self.instance_variable_defined?(:@values)
+ elsif instance_variable_defined?(:@values)
scrub_values(@values)
end
end
- def recursive(arg = nil)
- set_or_return(
- :recursive,
- arg,
- :kind_of => [TrueClass, FalseClass]
- )
- end
-
- def architecture(arg = nil)
- set_or_return(
- :architecture,
- arg,
- :kind_of => Symbol
- )
- end
+ property :recursive, [TrueClass, FalseClass], default: false
+ property :architecture, Symbol, default: :machine, equal_to: %i{machine x86_64 i386}
private
@@ -135,7 +215,7 @@ class Chef
# Some data types may raise errors when sent as json. Returns true if this
# value's data may need to be converted to a checksum.
def needs_checksum?(value)
- unsafe_types = [:binary, :dword, :dword_big_endian, :qword]
+ unsafe_types = %i{binary dword dword_big_endian qword}
unsafe_types.include?(value[:type])
end
diff --git a/lib/chef/resource/remote_directory.rb b/lib/chef/resource/remote_directory.rb
index 6e2928f3eb..b87fe8c085 100644
--- a/lib/chef/resource/remote_directory.rb
+++ b/lib/chef/resource/remote_directory.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,106 +17,71 @@
# limitations under the License.
#
-require "chef/resource/directory"
-require "chef/provider/remote_directory"
-require "chef/mixin/securable"
+require_relative "directory"
+require_relative "../provider/remote_directory"
+require_relative "../mixin/securable"
class Chef
class Resource
class RemoteDirectory < Chef::Resource::Directory
include Chef::Mixin::Securable
+ unified_mode true
- identity_attr :path
+ provides :remote_directory
- state_attrs :files_owner, :files_group, :files_mode
+ description "Use the **remote_directory** resource to incrementally transfer a directory from a cookbook to a node. The directory that is copied from the cookbook should be located under `COOKBOOK_NAME/files/default/REMOTE_DIRECTORY`. The `remote_directory` resource will obey file specificity."
default_action :create
allowed_actions :create, :create_if_missing, :delete
def initialize(name, run_context = nil)
super
- @path = name
- @source = ::File.basename(name)
@delete = false
- @recursive = true
- @purge = false
- @files_backup = 5
- @files_owner = nil
- @files_group = nil
- @files_mode = 0644 unless Chef::Platform.windows?
- @overwrite = true
- @cookbook = nil
end
- if Chef::Platform.windows?
- # create a second instance of the 'rights' attribute
+ if ChefUtils.windows?
+ # create a second instance of the 'rights' attribute (property)
rights_attribute(:files_rights)
end
- def source(args = nil)
- set_or_return(
- :source,
- args,
- :kind_of => String
- )
- end
+ # This same property exists in the directory resource, but we need to change the default to true here.
+ property :recursive, [ TrueClass, FalseClass ],
+ description: "Create or delete parent directories recursively. For the owner, group, and mode properties, the value of this attribute applies only to the leaf directory.",
+ default: true, desired_state: false
- def files_backup(arg = nil)
- set_or_return(
- :files_backup,
- arg,
- :kind_of => [ Integer, FalseClass ]
- )
- end
+ property :source, String,
+ description: "The base name of the source file (and inferred from the path property).",
+ default_description: "The base portion of the 'path' property. For example '/some/path/' would be 'path'.",
+ default: lazy { ::File.basename(path) }, desired_state: false
- def purge(arg = nil)
- set_or_return(
- :purge,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
+ property :files_backup, [ Integer, FalseClass ],
+ description: "The number of backup copies to keep for files in the directory.",
+ default: 5, desired_state: false
- def files_group(arg = nil)
- set_or_return(
- :files_group,
- arg,
- :regex => Chef::Config[:group_valid_regex]
- )
- end
+ property :purge, [ TrueClass, FalseClass ],
+ description: "Purge extra files found in the target directory.",
+ default: false, desired_state: false
- def files_mode(arg = nil)
- set_or_return(
- :files_mode,
- arg,
- :regex => /^\d{3,4}$/
- )
- end
+ property :overwrite, [ TrueClass, FalseClass ],
+ description: "Overwrite a file when it is different.",
+ default: true, desired_state: false
- def files_owner(arg = nil)
- set_or_return(
- :files_owner,
- arg,
- :regex => Chef::Config[:user_valid_regex]
- )
- end
+ property :cookbook, String,
+ description: "The cookbook in which a file is located (if it is not located in the current cookbook). The default value is the current cookbook.",
+ desired_state: false
- def overwrite(arg = nil)
- set_or_return(
- :overwrite,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
+ property :files_group, [String, Integer],
+ description: "Configure group permissions for files. A string or ID that identifies the group owner by group name, including fully qualified group names such as domain\\group or group@domain. If this value is not specified, existing groups remain unchanged and new group assignments use the default POSIX group (if available).",
+ regex: Chef::Config[:group_valid_regex]
- def cookbook(args = nil)
- set_or_return(
- :cookbook,
- args,
- :kind_of => String
- )
- end
+ property :files_mode, [String, Integer, nil],
+ description: "The octal mode for a file.\n UNIX- and Linux-based systems: A quoted 3-5 character string that defines the octal mode that is passed to chmod. For example: '755', '0755', or 00755. If the value is specified as a quoted string, it works exactly as if the chmod command was passed. If the value is specified as an integer, prepend a zero (0) to the value to ensure that it is interpreted as an octal number. For example, to assign read, write, and execute rights for all users, use '0777' or '777'; for the same rights, plus the sticky bit, use 01777 or '1777'.\n Microsoft Windows: A quoted 3-5 character string that defines the octal mode that is translated into rights for Microsoft Windows security. For example: '755', '0755', or 00755. Values up to '0777' are allowed (no sticky bits) and mean the same in Microsoft Windows as they do in UNIX, where 4 equals GENERIC_READ, 2 equals GENERIC_WRITE, and 1 equals GENERIC_EXECUTE. This property cannot be used to set :full_control. This property has no effect if not specified, but when it and rights are both specified, the effects are cumulative.",
+ default_description: "0644 on *nix systems",
+ regex: /^\d{3,4}$/, default: lazy { 0644 unless Chef::Platform.windows? }
+ property :files_owner, [String, Integer],
+ description: "Configure owner permissions for files. A string or ID that identifies the group owner by user name, including fully qualified user names such as domain\\user or user@domain. If this value is not specified, existing owners remain unchanged and new owner assignments use the current user (when necessary).",
+ regex: Chef::Config[:user_valid_regex]
end
end
end
diff --git a/lib/chef/resource/remote_file.rb b/lib/chef/resource/remote_file.rb
index 4a1d1c6cff..ac0b2fe6a7 100644
--- a/lib/chef/resource/remote_file.rb
+++ b/lib/chef/resource/remote_file.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Seth Chisamore (<schisamo@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,25 +17,26 @@
# limitations under the License.
#
-require "uri"
-require "chef/resource/file"
-require "chef/provider/remote_file"
-require "chef/mixin/securable"
-require "chef/mixin/uris"
+require "uri" unless defined?(URI)
+require_relative "file"
+require_relative "../provider/remote_file"
+require_relative "../mixin/securable"
+require_relative "../mixin/uris"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Resource
class RemoteFile < Chef::Resource::File
include Chef::Mixin::Securable
+ unified_mode true
+
+ provides :remote_file
+
+ description "Use the **remote_file** resource to transfer a file from a remote location using file specificity. This resource is similar to the **file** resource. Note: Fetching files from the `files/` directory in a cookbook should be done with the **cookbook_file** resource."
def initialize(name, run_context = nil)
super
@source = []
- @use_etag = true
- @use_last_modified = true
- @ftp_active_mode = false
- @headers = {}
- @provider = Chef::Provider::RemoteFile
end
# source can take any of the following as arguments
@@ -49,10 +50,10 @@ class Chef
def source(*args)
arg = parse_source_args(args)
ret = set_or_return(:source,
- arg,
- { :callbacks => {
- :validate_source => method(:validate_source),
- } })
+ arg,
+ { callbacks: {
+ validate_source: method(:validate_source),
+ } })
if ret.is_a? String
Array(ret)
else
@@ -72,13 +73,8 @@ class Chef
end
end
- def checksum(args = nil)
- set_or_return(
- :checksum,
- args,
- :kind_of => String
- )
- end
+ property :checksum, String,
+ description: "Optional, see `use_conditional_get`. The SHA-256 checksum of the file. Use to prevent a file from being re-downloaded. When the local file matches the checksum, #{ChefUtils::Dist::Infra::PRODUCT} does not download it."
# Disable or enable ETag and Last Modified conditional GET. Equivalent to
# use_etag(true_or_false)
@@ -88,47 +84,92 @@ class Chef
use_last_modified(true_or_false)
end
- def use_etag(args = nil)
- set_or_return(
- :use_etag,
- args,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
+ property :use_etag, [ TrueClass, FalseClass ], default: true,
+ description: "Enable ETag headers. Set to false to disable ETag headers. To use this setting, `use_conditional_get` must also be set to true."
alias :use_etags :use_etag
- def use_last_modified(args = nil)
- set_or_return(
- :use_last_modified,
- args,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
+ property :use_last_modified, [ TrueClass, FalseClass ], default: true,
+ description: "Enable `If-Modified-Since` headers. Set to `false` to disable `If-Modified-Since` headers. To use this setting, `use_conditional_get` must also be set to `true`."
+
+ property :ftp_active_mode, [ TrueClass, FalseClass ], default: false,
+ description: "Whether #{ChefUtils::Dist::Infra::PRODUCT} uses active or passive FTP. Set to `true` to use active FTP."
+
+ property :headers, Hash, default: lazy { {} },
+ description: "A Hash of custom HTTP headers."
+
+ property :show_progress, [ TrueClass, FalseClass ], default: false
+
+ property :ssl_verify_mode, Symbol, equal_to: %i{verify_none verify_peer},
+ introduced: "16.2",
+ description: "Optional property to override SSL policy. If not specified, uses the SSL policy from `config.rb`."
+
+ property :remote_user, String,
+ introduced: "13.4",
+ description: '**Windows only** The name of a user with access to the remote file specified by the source property. The user name may optionally be specified with a domain, such as: `domain\user` or `user@my.dns.domain.com` via Universal Principal Name (UPN) format. The domain may also be set using the `remote_domain` property. Note that this property is ignored if source is not a UNC path. If this property is specified, the `remote_password` property is required.'
+
+ property :remote_domain, String,
+ introduced: "13.4",
+ description: "**Windows only** The domain of the user specified by the `remote_user` property. By default the resource will authenticate against the domain of the remote system, or as a local account if the remote system is not joined to a domain. If the remote system is not part of a domain, it is necessary to authenticate as a local user on the remote system by setting the domain to `.`, for example: remote_domain '.'. The domain may also be specified as part of the `remote_user` property."
+
+ property :remote_password, String, sensitive: true,
+ introduced: "13.4",
+ description: "**Windows only** The password of the user specified by the `remote_user` property. This property is required if `remote_user` is specified and may only be specified if `remote_user` is specified. The `sensitive` property for this resource will automatically be set to `true` if `remote_password` is specified."
+
+ property :authentication, Symbol, equal_to: %i{remote local}, default: :remote
- def ftp_active_mode(args = nil)
- set_or_return(
- :ftp_active_mode,
- args,
- :kind_of => [ TrueClass, FalseClass ]
- )
+ def after_created
+ validate_identity_platform(remote_user, remote_password, remote_domain)
+ identity = qualify_user(remote_user, remote_password, remote_domain)
+ remote_domain(identity[:domain])
+ remote_user(identity[:user])
end
- def headers(args = nil)
- set_or_return(
- :headers,
- args,
- :kind_of => Hash
- )
+ def validate_identity_platform(specified_user, password = nil, specified_domain = nil)
+ if windows?
+ if specified_user && password.nil?
+ raise ArgumentError, "A value for `remote_password` must be specified when a value for `user` is specified on the Windows platform"
+ end
+ end
end
- def show_progress(args = nil)
- set_or_return(
- :show_progress,
- args,
- :default => false,
- :kind_of => [ TrueClass, FalseClass ]
- )
+ def qualify_user(specified_user, password = nil, specified_domain = nil)
+ domain = specified_domain
+ user = specified_user
+
+ if specified_user.nil? && ! specified_domain.nil?
+ raise ArgumentError, "The domain `#{specified_domain}` was specified, but no user name was given"
+ end
+
+ # if domain is provided in both username and domain
+ if specified_user && ((specified_user.include? '\\') || (specified_user.include? "@")) && specified_domain
+ raise ArgumentError, "The domain is provided twice. Username: `#{specified_user}`, Domain: `#{specified_domain}`. Please specify domain only once."
+ end
+
+ if ! specified_user.nil? && specified_domain.nil?
+ # Splitting username of format: Domain\Username
+ domain_and_user = user.split('\\')
+
+ if domain_and_user.length == 2
+ domain = domain_and_user[0]
+ user = domain_and_user[1]
+ elsif domain_and_user.length == 1
+ # Splitting username of format: Username@Domain
+ domain_and_user = user.split("@")
+ if domain_and_user.length == 2
+ domain = domain_and_user[1]
+ user = domain_and_user[0]
+ elsif domain_and_user.length != 1
+ raise ArgumentError, "The specified user name `#{user}` is not a syntactically valid user name"
+ end
+ end
+ end
+
+ if ( password || domain ) && user.nil?
+ raise ArgumentError, "A value for `password` or `domain` was specified without specification of a value for `user`"
+ end
+
+ { domain: domain, user: user }
end
private
@@ -138,6 +179,7 @@ class Chef
def validate_source(source)
source = Array(source).flatten
raise ArgumentError, "#{resource_name} has an empty source" if source.empty?
+
source.each do |src|
unless absolute_uri?(src)
raise Exceptions::InvalidRemoteFileURI,
@@ -148,7 +190,7 @@ class Chef
end
def absolute_uri?(source)
- Chef::Provider::RemoteFile::Fetcher.network_share?(source) || (source.kind_of?(String) && as_uri(source).absolute?)
+ Chef::Provider::RemoteFile::Fetcher.network_share?(source) || (source.is_a?(String) && 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 ee90064a17..dfd2546f43 100644
--- a/lib/chef/resource/resource_notification.rb
+++ b/lib/chef/resource/resource_notification.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Ball (<tball@chef.io>)
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,20 +16,29 @@
# limitations under the License.
#
-require "chef/resource"
+require_relative "../resource"
class Chef
class Resource
+ # @author Tyler Ball
+ # @attr [Resource] resource the Chef resource object to notify to
+ # @attr [Action] action the action to notify
+ # @attr [Resource] notifying_resource the Chef resource performing the notification
class Notification
- attr_accessor :resource, :action, :notifying_resource
+ attr_accessor :resource, :action, :notifying_resource, :unified_mode
- def initialize(resource, action, notifying_resource)
+ def initialize(resource, action, notifying_resource, unified_mode = false)
@resource = resource
- @action = action
+ @action = action&.to_sym
@notifying_resource = notifying_resource
+ @unified_mode = unified_mode
end
+ # Is the current notification a duplicate of another notification
+ #
+ # @param [Notification] other_notification another notification object to compare to
+ # @return [Boolean] does the resource match
def duplicates?(other_notification)
unless other_notification.respond_to?(:resource) && other_notification.respond_to?(:action)
msg = "only duck-types of Chef::Resource::Notification can be checked for duplication "\
@@ -41,21 +50,27 @@ class Chef
# If resource and/or notifying_resource is not a resource object, this will look them up in the resource collection
# and fix the references from strings to actual Resource objects.
- def resolve_resource_reference(resource_collection)
- return resource if resource.kind_of?(Chef::Resource) && notifying_resource.kind_of?(Chef::Resource)
+ # @param [ResourceCollection] resource_collection
+ #
+ # @return [void]
+ def resolve_resource_reference(resource_collection, always_raise = false)
+ return resource if resource.is_a?(Chef::Resource) && notifying_resource.is_a?(Chef::Resource)
- if not(resource.kind_of?(Chef::Resource))
- fix_resource_reference(resource_collection)
+ unless resource.is_a?(Chef::Resource)
+ fix_resource_reference(resource_collection, always_raise)
end
- if not(notifying_resource.kind_of?(Chef::Resource))
+ unless notifying_resource.is_a?(Chef::Resource)
fix_notifier_reference(resource_collection)
end
end
# This will look up the resource if it is not a Resource Object. It will complain if it finds multiple
# resources, can't find a resource, or gets invalid syntax.
- def fix_resource_reference(resource_collection)
+ # @param [ResourceCollection] resource_collection
+ #
+ # @return [void]
+ def fix_resource_reference(resource_collection, always_raise = false)
matching_resource = resource_collection.find(resource)
if Array(matching_resource).size > 1
msg = "Notification #{self} from #{notifying_resource} was created with a reference to multiple resources, "\
@@ -65,18 +80,21 @@ class Chef
self.resource = matching_resource
rescue Chef::Exceptions::ResourceNotFound => e
- err = Chef::Exceptions::ResourceNotFound.new(<<-FAIL)
-resource #{notifying_resource} is configured to notify resource #{resource} with action #{action}, \
-but #{resource} cannot be found in the resource collection. #{notifying_resource} is defined in \
-#{notifying_resource.source_line}
- FAIL
- err.set_backtrace(e.backtrace)
- raise err
+ # in unified mode we allow lazy notifications to resources not yet declared
+ if !unified_mode || always_raise
+ err = Chef::Exceptions::ResourceNotFound.new(<<~FAIL)
+ resource #{notifying_resource} is configured to notify resource #{resource} with action #{action}, \
+ but #{resource} cannot be found in the resource collection. #{notifying_resource} is defined in \
+ #{notifying_resource.source_line}
+ FAIL
+ err.set_backtrace(e.backtrace)
+ raise err
+ end
rescue Chef::Exceptions::InvalidResourceSpecification => e
- err = Chef::Exceptions::InvalidResourceSpecification.new(<<-F)
-Resource #{notifying_resource} is configured to notify resource #{resource} with action #{action}, \
-but #{resource.inspect} is not valid syntax to look up a resource in the resource collection. Notification \
-is defined near #{notifying_resource.source_line}
+ err = Chef::Exceptions::InvalidResourceSpecification.new(<<~F)
+ Resource #{notifying_resource} is configured to notify resource #{resource} with action #{action}, \
+ but #{resource.inspect} is not valid syntax to look up a resource in the resource collection. Notification \
+ is defined near #{notifying_resource.source_line}
F
err.set_backtrace(e.backtrace)
raise err
@@ -84,6 +102,9 @@ is defined near #{notifying_resource.source_line}
# This will look up the notifying_resource if it is not a Resource Object. It will complain if it finds multiple
# resources, can't find a resource, or gets invalid syntax.
+ # @param [ResourceCollection] resource_collection
+ #
+ # @return [void]
def fix_notifier_reference(resource_collection)
matching_notifier = resource_collection.find(notifying_resource)
if Array(matching_notifier).size > 1
@@ -95,18 +116,18 @@ is defined near #{notifying_resource.source_line}
self.notifying_resource = matching_notifier
rescue Chef::Exceptions::ResourceNotFound => e
- err = Chef::Exceptions::ResourceNotFound.new(<<-FAIL)
-Resource #{resource} is configured to receive notifications from #{notifying_resource} with action #{action}, \
-but #{notifying_resource} cannot be found in the resource collection. #{resource} is defined in \
-#{resource.source_line}
+ err = Chef::Exceptions::ResourceNotFound.new(<<~FAIL)
+ Resource #{resource} is configured to receive notifications from #{notifying_resource} with action #{action}, \
+ but #{notifying_resource} cannot be found in the resource collection. #{resource} is defined in \
+ #{resource.source_line}
FAIL
err.set_backtrace(e.backtrace)
raise err
rescue Chef::Exceptions::InvalidResourceSpecification => e
- err = Chef::Exceptions::InvalidResourceSpecification.new(<<-F)
-Resource #{resource} is configured to receive notifications from #{notifying_resource} with action #{action}, \
-but #{notifying_resource.inspect} is not valid syntax to look up a resource in the resource collection. Notification \
-is defined near #{resource.source_line}
+ err = Chef::Exceptions::InvalidResourceSpecification.new(<<~F)
+ Resource #{resource} is configured to receive notifications from #{notifying_resource} with action #{action}, \
+ but #{notifying_resource.inspect} is not valid syntax to look up a resource in the resource collection. Notification \
+ is defined near #{resource.source_line}
F
err.set_backtrace(e.backtrace)
raise err
@@ -114,6 +135,7 @@ is defined near #{resource.source_line}
def ==(other)
return false unless other.is_a?(self.class)
+
other.resource == resource && other.action == action && other.notifying_resource == notifying_resource
end
diff --git a/lib/chef/resource/rhsm_errata.rb b/lib/chef/resource/rhsm_errata.rb
new file mode 100644
index 0000000000..7a1e3df325
--- /dev/null
+++ b/lib/chef/resource/rhsm_errata.rb
@@ -0,0 +1,50 @@
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class RhsmErrata < Chef::Resource
+ unified_mode true
+ provides(:rhsm_errata) { true }
+
+ description "Use the **rhsm_errata** resource to install packages associated with a given Red Hat Subscription Manager Errata ID. This is helpful if packages to mitigate a single vulnerability must be installed on your hosts."
+ introduced "14.0"
+
+ property :errata_id, String,
+ description: "An optional property for specifying the errata ID if it differs from the resource block's name.",
+ name_property: true
+
+ action :install do
+ description "Installs a package for a specific errata ID."
+
+ execute "Install errata packages for #{new_resource.errata_id}" do
+ command "#{package_manager_command} update --advisory #{new_resource.errata_id} -y"
+ default_env true
+ action :run
+ end
+ end
+
+ action_class do
+ def package_manager_command
+ node["platform_version"].to_i >= 8 ? "dnf" : "yum"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/rhsm_errata_level.rb b/lib/chef/resource/rhsm_errata_level.rb
new file mode 100644
index 0000000000..9ac3944153
--- /dev/null
+++ b/lib/chef/resource/rhsm_errata_level.rb
@@ -0,0 +1,56 @@
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class RhsmErrataLevel < Chef::Resource
+ unified_mode true
+ provides(:rhsm_errata_level) { true }
+
+ description "Use the **rhsm_errata_level** resource to install all packages of a specified errata level from the Red Hat Subscription Manager. For example, you can ensure that all packages associated with errata marked at a 'Critical' security level are installed."
+ introduced "14.0"
+
+ property :errata_level, String,
+ coerce: proc { |x| x.downcase },
+ equal_to: %w{critical moderate important low},
+ description: "An optional property for specifying the errata level of packages to install if it differs from the resource block's name.",
+ name_property: true
+
+ action :install do
+ description "Install all packages of the specified errata level."
+
+ if rhel6?
+ yum_package "yum-plugin-security"
+ end
+
+ execute "Install any #{new_resource.errata_level} errata" do
+ command "#{package_manager_command} update --sec-severity=#{new_resource.errata_level.capitalize} -y"
+ default_env true
+ action :run
+ end
+ end
+
+ action_class do
+ def package_manager_command
+ node["platform_version"].to_i >= 8 ? "dnf" : "yum"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/rhsm_register.rb b/lib/chef/resource/rhsm_register.rb
new file mode 100644
index 0000000000..07c4dbc8d7
--- /dev/null
+++ b/lib/chef/resource/rhsm_register.rb
@@ -0,0 +1,195 @@
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+require "shellwords" unless defined?(Shellwords)
+
+class Chef
+ class Resource
+ class RhsmRegister < Chef::Resource
+ unified_mode true
+ provides(:rhsm_register) { true }
+
+ description "Use the **rhsm_register** resource to register a node with the Red Hat Subscription Manager or a local Red Hat Satellite server."
+ introduced "14.0"
+
+ property :activation_key, [String, Array],
+ coerce: proc { |x| Array(x) },
+ description: "A string or array of activation keys to use when registering; you must also specify the 'organization' property when using this property."
+
+ property :satellite_host, String,
+ description: "The FQDN of the Satellite host to register with. If this property is not specified, the host will register with Red Hat's public RHSM service."
+
+ property :organization, String,
+ description: "The organization to use when registering; required when using the 'activation_key' property."
+
+ property :environment, String,
+ description: "The environment to use when registering; required when using the username and password properties."
+
+ property :username, String,
+ description: "The username to use when registering. This property is not applicable if using an activation key. If specified, password and environment properties are also required."
+
+ property :password, String,
+ description: "The password to use when registering. This property is not applicable if using an activation key. If specified, username and environment are also required."
+
+ property :system_name, String,
+ description: "The name of the system to register, defaults to the hostname.",
+ introduced: "16.5"
+
+ property :auto_attach,
+ [TrueClass, FalseClass],
+ description: "If true, RHSM will attempt to automatically attach the host to applicable subscriptions. It is generally better to use an activation key with the subscriptions pre-defined.",
+ default: false
+
+ property :install_katello_agent, [TrueClass, FalseClass],
+ description: "If true, the 'katello-agent' RPM will be installed.",
+ default: true
+
+ property :force, [TrueClass, FalseClass],
+ description: "If true, the system will be registered even if it is already registered. Normally, any register operations will fail if the machine has already been registered.",
+ default: false, desired_state: false
+
+ property :https_for_ca_consumer, [TrueClass, FalseClass],
+ description: "If true, #{ChefUtils::Dist::Infra::PRODUCT} will fetch the katello-ca-consumer-latest.noarch.rpm from the satellite_host using HTTPS.",
+ default: false, desired_state: false,
+ introduced: "15.9"
+
+ action :register do
+ description "Register the node with RHSM."
+
+ package "subscription-manager"
+
+ unless new_resource.satellite_host.nil? || registered_with_rhsm?
+ declare_resource(package_resource, "katello-ca-consumer-latest") do
+ options "--nogpgcheck"
+ source "#{Chef::Config[:file_cache_path]}/katello-package.rpm"
+ action :nothing
+ end
+
+ remote_file "#{Chef::Config[:file_cache_path]}/katello-package.rpm" do
+ source ca_consumer_package_source
+ action :create
+ notifies :install, "#{package_resource}[katello-ca-consumer-latest]", :immediately
+ not_if { katello_cert_rpm_installed? }
+ end
+
+ file "#{Chef::Config[:file_cache_path]}/katello-package.rpm" do
+ action :delete
+ end
+ end
+
+ execute "Register to RHSM" do
+ sensitive new_resource.sensitive
+ command register_command
+ default_env true
+ action :run
+ not_if { registered_with_rhsm? } unless new_resource.force
+ end
+
+ if new_resource.install_katello_agent && !new_resource.satellite_host.nil?
+ package "katello-agent"
+ end
+ end
+
+ action :unregister do
+ description "Unregister the node from RHSM."
+
+ execute "Unregister from RHSM" do
+ command "subscription-manager unregister"
+ default_env true
+ action :run
+ only_if { registered_with_rhsm? }
+ notifies :run, "execute[Clean RHSM Config]", :immediately
+ end
+
+ execute "Clean RHSM Config" do
+ command "subscription-manager clean"
+ default_env true
+ action :nothing
+ end
+ end
+
+ action_class do
+ #
+ # @return [Symbol] dnf_package or yum_package depending on OS release
+ #
+ def package_resource
+ node["platform_version"].to_i >= 8 ? :dnf_package : :yum_package
+ end
+
+ #
+ # @return [Boolean] is the node registered with RHSM
+ #
+ def registered_with_rhsm?
+ @registered ||= !shell_out("subscription-manager status").stdout.include?("Overall Status: Unknown")
+ end
+
+ #
+ # @return [Boolean] is katello-ca-consumer installed
+ #
+ def katello_cert_rpm_installed?
+ shell_out("rpm -qa").stdout.include?("katello-ca-consumer")
+ end
+
+ #
+ # @return [String] The URI to fetch katello-ca-consumer-latest.noarch.rpm from
+ #
+ def ca_consumer_package_source
+ protocol = new_resource.https_for_ca_consumer ? "https" : "http"
+ "#{protocol}://#{new_resource.satellite_host}/pub/katello-ca-consumer-latest.noarch.rpm"
+ end
+
+ def register_command
+ command = %w{subscription-manager register}
+
+ if new_resource.activation_key
+ unless new_resource.activation_key.empty?
+ raise "Unable to register - you must specify organization when using activation keys" if new_resource.organization.nil?
+
+ command << new_resource.activation_key.map { |key| "--activationkey=#{Shellwords.shellescape(key)}" }
+ command << "--org=#{Shellwords.shellescape(new_resource.organization)}"
+ command << "--name=#{Shellwords.shellescape(new_resource.system_name)}" if new_resource.system_name
+ command << "--force" if new_resource.force
+
+ return command.join(" ")
+ end
+ end
+
+ if new_resource.username && new_resource.password
+ raise "Unable to register - you must specify environment when using username/password" if new_resource.environment.nil? && using_satellite_host?
+
+ command << "--username=#{Shellwords.shellescape(new_resource.username)}"
+ command << "--password=#{Shellwords.shellescape(new_resource.password)}"
+ command << "--environment=#{Shellwords.shellescape(new_resource.environment)}" if using_satellite_host?
+ command << "--name=#{Shellwords.shellescape(new_resource.system_name)}" if new_resource.system_name
+ command << "--auto-attach" if new_resource.auto_attach
+ command << "--force" if new_resource.force
+
+ return command.join(" ")
+ end
+
+ raise "Unable to create register command - you must specify activation_key or username/password"
+ end
+
+ def using_satellite_host?
+ !new_resource.satellite_host.nil?
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/rhsm_repo.rb b/lib/chef/resource/rhsm_repo.rb
new file mode 100644
index 0000000000..d8959695cf
--- /dev/null
+++ b/lib/chef/resource/rhsm_repo.rb
@@ -0,0 +1,66 @@
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class RhsmRepo < Chef::Resource
+ unified_mode true
+
+ provides(:rhsm_repo) { true }
+
+ description "Use the **rhsm_repo** resource to enable or disable Red Hat Subscription Manager repositories that are made available via attached subscriptions."
+ introduced "14.0"
+
+ property :repo_name, String,
+ description: "An optional property for specifying the repository name if it differs from the resource block's name.",
+ name_property: true
+
+ action :enable do
+ description "Enable a RHSM repository."
+
+ execute "Enable repository #{new_resource.repo_name}" do
+ command "subscription-manager repos --enable=#{new_resource.repo_name}"
+ default_env true
+ action :run
+ not_if { repo_enabled?(new_resource.repo_name) }
+ end
+ end
+
+ action :disable do
+ description "Disable a RHSM repository."
+
+ execute "Enable repository #{new_resource.repo_name}" do
+ command "subscription-manager repos --disable=#{new_resource.repo_name}"
+ default_env true
+ action :run
+ only_if { repo_enabled?(new_resource.repo_name) }
+ end
+ end
+
+ action_class do
+ def repo_enabled?(repo)
+ # FIXME: use shell_out()
+ cmd = Mixlib::ShellOut.new("subscription-manager repos --list-enabled", env: { LANG: "en_US" })
+ cmd.run_command
+ repo == "*" || !cmd.stdout.match(/Repo ID:\s+#{repo}$/).nil?
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/rhsm_subscription.rb b/lib/chef/resource/rhsm_subscription.rb
new file mode 100644
index 0000000000..15a4822ecd
--- /dev/null
+++ b/lib/chef/resource/rhsm_subscription.rb
@@ -0,0 +1,99 @@
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class RhsmSubscription < Chef::Resource
+ unified_mode true
+
+ provides(:rhsm_subscription) { true }
+
+ description "Use the **rhsm_subscription** resource to add or remove Red Hat Subscription Manager subscriptions from your host. This can be used when a host's activation_key does not attach all necessary subscriptions to your host."
+ introduced "14.0"
+
+ property :pool_id, String,
+ description: "An optional property for specifying the Pool ID if it differs from the resource block's name.",
+ name_property: true
+
+ action :attach do
+ description "Attach the node to a subscription pool."
+
+ execute "Attach subscription pool #{new_resource.pool_id}" do
+ command "subscription-manager attach --pool=#{new_resource.pool_id}"
+ default_env true
+ action :run
+ not_if { subscription_attached?(new_resource.pool_id) }
+ end
+ end
+
+ action :remove do
+ description "Remove the node from a subscription pool."
+
+ execute "Remove subscription pool #{new_resource.pool_id}" do
+ command "subscription-manager remove --serial=#{pool_serial(new_resource.pool_id)}"
+ default_env true
+ action :run
+ only_if { subscription_attached?(new_resource.pool_id) }
+ end
+ end
+
+ action_class do
+ def subscription_attached?(subscription)
+ # FIXME: use shell_out
+ cmd = Mixlib::ShellOut.new("subscription-manager list --consumed | grep #{subscription}", env: { LANG: "en_US" })
+ cmd.run_command
+ !cmd.stdout.match(/Pool ID:\s+#{subscription}$/).nil?
+ end
+
+ def serials_by_pool
+ serials = {}
+ pool = nil
+ serial = nil
+
+ # FIXME: use shell_out
+ cmd = Mixlib::ShellOut.new("subscription-manager list --consumed", env: { LANG: "en_US" })
+ cmd.run_command
+ cmd.stdout.lines.each do |line|
+ line.strip!
+ key, value = line.split(/:\s+/, 2)
+ next unless ["Pool ID", "Serial"].include?(key)
+
+ if key == "Pool ID"
+ pool = value
+ elsif key == "Serial"
+ serial = value
+ end
+
+ next unless pool && serial
+
+ serials[pool] = serial
+ pool = nil
+ serial = nil
+ end
+
+ serials
+ end
+
+ def pool_serial(pool_id)
+ serials_by_pool[pool_id]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/route.rb b/lib/chef/resource/route.rb
index 0117a8bfc0..82f961679b 100644
--- a/lib/chef/resource/route.rb
+++ b/lib/chef/resource/route.rb
@@ -17,121 +17,44 @@
# limitations under the License.
#
-require "chef/resource"
+require_relative "../resource"
class Chef
class Resource
class Route < Chef::Resource
- identity_attr :target
+ unified_mode true
- state_attrs :netmask, :gateway
+ provides :route
default_action :add
allowed_actions :add, :delete
- def initialize(name, run_context = nil)
- super
- @target = name
- @netmask = nil
- @gateway = nil
- @metric = nil
- @device = nil
- @route_type = :host
- @networking = nil
- @networking_ipv6 = nil
- @hostname = nil
- @domainname = nil
- @domain = nil
- end
+ description "Use the **route** resource to manage the system routing table in a Linux environment."
- def networking(arg = nil)
- set_or_return(
- :networking,
- arg,
- :kind_of => String
- )
- end
+ property :target, String,
+ description: "The IP address of the target route.",
+ name_property: true
- def networking_ipv6(arg = nil)
- set_or_return(
- :networking_ipv6,
- arg,
- :kind_of => String
- )
- end
+ property :comment, [String, nil],
+ description: "Add a comment for the route.",
+ introduced: "14.0"
- def hostname(arg = nil)
- set_or_return(
- :hostname,
- arg,
- :kind_of => String
- )
- end
+ property :metric, [Integer, nil],
+ description: "The route metric value."
- def domainname(arg = nil)
- set_or_return(
- :domainname,
- arg,
- :kind_of => String
- )
- end
+ property :netmask, [String, nil],
+ description: "The decimal representation of the network mask. For example: `255.255.255.0`."
- def domain(arg = nil)
- set_or_return(
- :domain,
- arg,
- :kind_of => String
- )
- end
+ property :gateway, [String, nil],
+ description: "The gateway for the route."
- def target(arg = nil)
- set_or_return(
- :target,
- arg,
- :kind_of => String
- )
- end
+ property :device, [String, nil],
+ description: "The network interface to which the route applies.",
+ desired_state: false # Has a partial default in the provider of eth0.
- def netmask(arg = nil)
- set_or_return(
- :netmask,
- arg,
- :kind_of => String
- )
- end
-
- def gateway(arg = nil)
- set_or_return(
- :gateway,
- arg,
- :kind_of => String
- )
- end
-
- def metric(arg = nil)
- set_or_return(
- :metric,
- arg,
- :kind_of => Integer
- )
- end
-
- def device(arg = nil)
- set_or_return(
- :device,
- arg,
- :kind_of => String
- )
- end
-
- def route_type(arg = nil)
- real_arg = arg.kind_of?(String) ? arg.to_sym : arg
- set_or_return(
- :route_type,
- real_arg,
- :equal_to => [ :host, :net ]
- )
- end
+ property :route_type, [Symbol, String],
+ description: "",
+ equal_to: %i{host net}, default: :host, desired_state: false
end
end
end
diff --git a/lib/chef/resource/rpm_package.rb b/lib/chef/resource/rpm_package.rb
index c93dfecaf5..3f65e32cef 100644
--- a/lib/chef/resource/rpm_package.rb
+++ b/lib/chef/resource/rpm_package.rb
@@ -16,17 +16,28 @@
# limitations under the License.
#
-require "chef/resource/package"
-require "chef/provider/package/rpm"
+require_relative "package"
class Chef
class Resource
class RpmPackage < Chef::Resource::Package
- resource_name :rpm_package
- provides :rpm_package, os: %w{linux aix}
+ unified_mode true
- property :allow_downgrade, [ true, false ], default: false, desired_state: false
+ provides :rpm_package
+ description "Use the **rpm_package** resource to manage packages using the RPM Package Manager."
+
+ property :allow_downgrade, [ TrueClass, FalseClass ],
+ description: "Allow downgrading a package to satisfy requested version requirements.",
+ default: true,
+ desired_state: false
+
+ property :package_name, String,
+ description: "An optional property to set the package name if it differs from the resource block's name.",
+ identity: true
+
+ property :version, String,
+ description: "The version of a package to be installed or upgraded."
end
end
end
diff --git a/lib/chef/resource/ruby.rb b/lib/chef/resource/ruby.rb
index 91805a1db6..2c0e65e9da 100644
--- a/lib/chef/resource/ruby.rb
+++ b/lib/chef/resource/ruby.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,12 +16,17 @@
# limitations under the License.
#
-require "chef/resource/script"
-require "chef/provider/script"
+require_relative "script"
class Chef
class Resource
class Ruby < Chef::Resource::Script
+ unified_mode true
+
+ provides :ruby
+
+ description "Use the **ruby** resource to execute scripts using the Ruby interpreter. This resource may also use any of the actions and properties that are available to the **execute** resource. Commands that are executed with this resource are (by their nature) not idempotent, as they are typically unique to the environment in which they are run. Use `not_if` and `only_if` to guard this resource for idempotence."
+
def initialize(name, run_context = nil)
super
@interpreter = "ruby"
diff --git a/lib/chef/resource/ruby_block.rb b/lib/chef/resource/ruby_block.rb
index 87a4cfb7c5..2d7d2fe8b6 100644
--- a/lib/chef/resource/ruby_block.rb
+++ b/lib/chef/resource/ruby_block.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: AJ Christensen (<aj@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,21 +17,21 @@
# limitations under the License.
#
-require "chef/resource"
-require "chef/provider/ruby_block"
+require_relative "../resource"
+require_relative "../provider/ruby_block"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Resource
class RubyBlock < Chef::Resource
- default_action :run
- allowed_actions :create, :run
+ unified_mode true
- identity_attr :block_name
+ provides :ruby_block, target_mode: true
- def initialize(name, run_context = nil)
- super
- @block_name = name
- end
+ description "Use the **ruby_block** resource to execute Ruby code during a #{ChefUtils::Dist::Infra::PRODUCT} run. Ruby code in the `ruby_block` resource is evaluated with other resources during convergence, whereas Ruby code outside of a `ruby_block` resource is evaluated before other resources, as the recipe is compiled."
+
+ default_action :run
+ allowed_actions :create, :run
def block(&block)
if block_given? && block
@@ -41,13 +41,7 @@ class Chef
end
end
- def block_name(arg = nil)
- set_or_return(
- :block_name,
- arg,
- :kind_of => String
- )
- end
+ property :block_name, String, name_property: true
end
end
end
diff --git a/lib/chef/resource/scm.rb b/lib/chef/resource/scm.rb
deleted file mode 100644
index 1e8c71e59d..0000000000
--- a/lib/chef/resource/scm.rb
+++ /dev/null
@@ -1,185 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@kallistec.com>)
-# Copyright:: Copyright 2008-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/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
- @enable_submodules = false
- @enable_checkout = true
- @revision = "HEAD"
- @remote = "origin"
- @ssh_wrapper = nil
- @depth = nil
- @checkout_branch = "deploy"
- @environment = nil
- end
-
- def destination(arg = nil)
- set_or_return(
- :destination,
- arg,
- :kind_of => String
- )
- end
-
- def repository(arg = nil)
- set_or_return(
- :repository,
- arg,
- :kind_of => String
- )
- end
-
- def revision(arg = nil)
- set_or_return(
- :revision,
- arg,
- :kind_of => String
- )
- end
-
- def user(arg = nil)
- set_or_return(
- :user,
- arg,
- :kind_of => [String, Integer]
- )
- end
-
- def group(arg = nil)
- set_or_return(
- :group,
- arg,
- :kind_of => [String, Integer]
- )
- end
-
- def svn_username(arg = nil)
- set_or_return(
- :svn_username,
- arg,
- :kind_of => String
- )
- end
-
- def svn_password(arg = nil)
- set_or_return(
- :svn_password,
- arg,
- :kind_of => String
- )
- end
-
- def svn_arguments(arg = nil)
- @svn_arguments, arg = nil, nil if arg == false
- set_or_return(
- :svn_arguments,
- arg,
- :kind_of => String
- )
- end
-
- def svn_info_args(arg = nil)
- @svn_info_args, arg = nil, nil if arg == false
- set_or_return(
- :svn_info_args,
- arg,
- :kind_of => String)
- end
-
- # Capistrano and git-deploy use ``shallow clone''
- def depth(arg = nil)
- set_or_return(
- :depth,
- arg,
- :kind_of => Integer
- )
- end
-
- def enable_submodules(arg = nil)
- set_or_return(
- :enable_submodules,
- arg,
- :kind_of => [TrueClass, FalseClass]
- )
- end
-
- def enable_checkout(arg = nil)
- set_or_return(
- :enable_checkout,
- arg,
- :kind_of => [TrueClass, FalseClass]
- )
- end
-
- def remote(arg = nil)
- set_or_return(
- :remote,
- arg,
- :kind_of => String
- )
- end
-
- def ssh_wrapper(arg = nil)
- set_or_return(
- :ssh_wrapper,
- arg,
- :kind_of => String
- )
- end
-
- def timeout(arg = nil)
- set_or_return(
- :timeout,
- arg,
- :kind_of => Integer
- )
- end
-
- def checkout_branch(arg = nil)
- set_or_return(
- :checkout_branch,
- arg,
- :kind_of => String
- )
- end
-
- def environment(arg = nil)
- set_or_return(
- :environment,
- arg,
- :kind_of => [ Hash ]
- )
- end
-
- alias :env :environment
- end
- end
-end
diff --git a/lib/chef/resource/scm/_scm.rb b/lib/chef/resource/scm/_scm.rb
new file mode 100644
index 0000000000..573d8b9b94
--- /dev/null
+++ b/lib/chef/resource/scm/_scm.rb
@@ -0,0 +1,50 @@
+#
+# Author:: Daniel DeLeo (<dan@kallistec.com>)
+# Copyright:: Copyright (c) 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.
+#
+
+unified_mode true
+
+default_action :sync
+allowed_actions :checkout, :export, :sync, :diff, :log
+
+property :destination, String,
+ description: "The location path to which the source is to be cloned, checked out, or exported. Default value: the name of the resource block.",
+ name_property: true
+
+property :repository, String,
+ description: "The URI of the code repository."
+
+property :revision, String,
+ description: "The revision to checkout.",
+ default: "HEAD"
+
+property :user, [String, Integer],
+ description: "The system user that will own the checked-out code.",
+ default_description: "`HOME` environment variable of the user running #{ChefUtils::Dist::Infra::CLIENT}"
+
+property :group, [String, Integer],
+ description: "The system group that will own the checked-out code."
+
+property :timeout, Integer,
+ description: "The amount of time (in seconds) to wait before timing out.",
+ desired_state: false
+
+property :environment, [Hash, nil],
+ description: "A Hash of environment variables in the form of ({'ENV_VARIABLE' => 'VALUE'}).",
+ default: nil
+
+alias :env :environment
diff --git a/lib/chef/resource/scm/git.rb b/lib/chef/resource/scm/git.rb
new file mode 100644
index 0000000000..8293d1ed4a
--- /dev/null
+++ b/lib/chef/resource/scm/git.rb
@@ -0,0 +1,144 @@
+#
+# Author:: Daniel DeLeo (<dan@kallistec.com>)
+# Copyright:: Copyright (c) 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_relative "../../resource"
+
+class Chef
+ class Resource
+ class Git < Chef::Resource
+ use "scm"
+
+ unified_mode true
+
+ provides :git
+
+ description "Use the **git** resource to manage source control resources that exist in a git repository. git version 1.6.5 (or higher) is required to use all of the functionality in the git resource."
+ examples <<~DOC
+ **Use the git mirror**
+
+ ```ruby
+ git '/opt/my_sources/couch' do
+ repository 'git://git.apache.org/couchdb.git'
+ revision 'master'
+ action :sync
+ end
+ ```
+
+ **Use different branches**
+
+ To use different branches, depending on the environment of the node:
+
+ ```ruby
+ branch_name = if node.chef_environment == 'QA'
+ 'staging'
+ else
+ 'master'
+ end
+
+ git '/home/user/deployment' do
+ repository 'git@github.com:git_site/deployment.git'
+ revision branch_name
+ action :sync
+ user 'user'
+ group 'test'
+ end
+ ```
+
+ Where the `branch_name` variable is set to staging or master, depending on the environment of the node. Once this is determined, the `branch_name` variable is used to set the revision for the repository. If the git status command is used after running the example above, it will return the branch name as `deploy`, as this is the default value. Run Chef Infra Client in debug mode to verify that the correct branches are being checked out:
+
+ ```
+ sudo chef-client -l debug
+ ```
+
+ **Install an application from git using bash**
+
+ The following example shows how Bash can be used to install a plug-in for rbenv named ruby-build, which is located in git version source control. First, the application is synchronized, and then Bash changes its working directory to the location in which ruby-build is located, and then runs a command.
+
+ ```ruby
+ git "#{Chef::Config[:file_cache_path]}/ruby-build" do
+ repository 'git://github.com/rbenv/ruby-build.git'
+ revision 'master'
+ action :sync
+ end
+
+ bash 'install_ruby_build' do
+ cwd "#{Chef::Config[:file_cache_path]}/ruby-build"
+ user 'rbenv'
+ group 'rbenv'
+ code <<-EOH
+ ./install.sh
+ EOH
+ environment 'PREFIX' => '/usr/local'
+ end
+ ```
+
+ **Notify a resource post-checkout**
+
+ ```ruby
+ git "#{Chef::Config[:file_cache_path]}/my_app" do
+ repository node['my_app']['git_repository']
+ revision node['my_app']['git_revision']
+ action :sync
+ notifies :run, 'bash[compile_my_app]', :immediately
+ end
+ ```
+
+ **Pass in environment variables**
+
+ ```ruby
+ git '/opt/my_sources/couch' do
+ repository 'git://git.apache.org/couchdb.git'
+ revision 'master'
+ environment 'VAR' => 'whatever'
+ action :sync
+ end
+ ```
+ DOC
+
+ property :additional_remotes, Hash,
+ description: "A Hash of additional remotes that are added to the git repository configuration.",
+ default: lazy { {} }
+
+ property :depth, Integer,
+ description: "The number of past revisions to be included in the git shallow clone. Unless specified the default behavior will do a full clone."
+
+ property :enable_submodules, [TrueClass, FalseClass],
+ description: "Perform a sub-module initialization and update.",
+ default: false
+
+ property :enable_checkout, [TrueClass, FalseClass],
+ description: "Check out a repo from master. Set to `false` when using the `checkout_branch` attribute to prevent the git resource from attempting to check out `master` from `master`.",
+ default: true
+
+ property :remote, String,
+ description: "The remote repository to use when synchronizing an existing clone.",
+ default: "origin"
+
+ property :ssh_wrapper, String,
+ desired_state: false,
+ description: "The path to the wrapper script used when running SSH with git. The `GIT_SSH` environment variable is set to this."
+
+ property :checkout_branch, String,
+ description: "Set this to use a local branch to avoid checking SHAs or tags to a detached head state."
+
+ alias :branch :revision
+ alias :reference :revision
+ alias :repo :repository
+ end
+ end
+end
diff --git a/lib/chef/resource/scm/subversion.rb b/lib/chef/resource/scm/subversion.rb
new file mode 100644
index 0000000000..db20787aa5
--- /dev/null
+++ b/lib/chef/resource/scm/subversion.rb
@@ -0,0 +1,73 @@
+#
+# Author:: Daniel DeLeo (<dan@kallistec.com>)
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 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-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Resource
+ class Subversion < Chef::Resource
+ use "scm"
+
+ unified_mode true
+
+ provides :subversion
+
+ description "Use the **subversion** resource to manage source control resources that exist in a Subversion repository."
+ examples <<~DOC
+ **Get the latest version of an application**
+
+ ```ruby
+ subversion 'CouchDB Edge' do
+ repository 'http://svn.apache.org/repos/asf/couchdb/trunk'
+ revision 'HEAD'
+ destination '/opt/my_sources/couch'
+ action :sync
+ end
+ ```
+ DOC
+
+ allowed_actions :force_export
+
+ property :svn_arguments, [String, nil, FalseClass],
+ description: "The extra arguments that are passed to the Subversion command.",
+ coerce: proc { |v| v == false ? nil : v }, # coerce false to nil
+ default: "--no-auth-cache"
+
+ property :svn_info_args, [String, nil, FalseClass],
+ description: "Use when the `svn info` command is used by #{ChefUtils::Dist::Infra::PRODUCT} and arguments need to be passed. The `svn_arguments` command does not work when the `svn info` command is used.",
+ coerce: proc { |v| v == false ? nil : v }, # coerce false to nil
+ default: "--no-auth-cache"
+
+ property :svn_binary, String,
+ description: "The location of the svn binary."
+
+ property :svn_username, String,
+ description: "The user name for a user that has access to the Subversion repository."
+
+ property :svn_password, String,
+ description: "The password for a user that has access to the Subversion repository.",
+ sensitive: true, desired_state: false
+
+ # 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
+ end
+ end
+end
diff --git a/lib/chef/resource/script.rb b/lib/chef/resource/script.rb
index 5173a76542..54468a534d 100644
--- a/lib/chef/resource/script.rb
+++ b/lib/chef/resource/script.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,58 +17,43 @@
# limitations under the License.
#
-require "chef/resource/execute"
-require "chef/provider/script"
+require_relative "execute"
class Chef
class Resource
class Script < Chef::Resource::Execute
- # Chef-13: go back to using :name as the identity attr
- identity_attr :command
+ unified_mode true
+
+ provides :script
+
+ identity_attr :name
+
+ description "Use the **script** resource to execute scripts using a specified interpreter, such as Bash, csh, Perl, Python, or Ruby."\
+ " This resource may also use any of the actions and properties that are available to the **execute** resource. Commands"\
+ " that are executed with this resource are (by their nature) not idempotent, as they are typically unique to the"\
+ " environment in which they are run. Use `not_if` and `only_if` to guard this resource for idempotence."
def initialize(name, run_context = nil)
super
- # Chef-13: the command variable should be initialized to nil
- @command = name
- @code = nil
- @interpreter = nil
- @flags = nil
+ @command = nil
@default_guard_interpreter = :default
end
+ # FIXME: remove this and use an execute sub-resource instead of inheriting from Execute
def command(arg = nil)
+ super
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"
+ raise Chef::Exceptions::Script, "Do not use the command property on a #{resource_name} resource, use the 'code' property instead."
end
- super
end
- def code(arg = nil)
- set_or_return(
- :code,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def interpreter(arg = nil)
- set_or_return(
- :interpreter,
- arg,
- :kind_of => [ String ]
- )
- end
+ property :code, String, required: true,
+ description: "A quoted string of code to be executed."
- def flags(arg = nil)
- set_or_return(
- :flags,
- arg,
- :kind_of => [ String ]
- )
- end
+ property :interpreter, String
+ property :flags, String,
+ description: "One or more command line flags that are passed to the interpreter when a command is invoked."
end
end
end
diff --git a/lib/chef/resource/service.rb b/lib/chef/resource/service.rb
index 1ca4b84af0..63674a3c93 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@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,140 +17,89 @@
# limitations under the License.
#
-require "chef/resource"
+require "chef-utils/dsl/service" unless defined?(ChefUtils::DSL::Service)
+require_relative "../resource"
+require "shellwords" unless defined?(Shellwords)
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Resource
class Service < Chef::Resource
- identity_attr :service_name
+ include Chef::Platform::ServiceHelpers
+ extend Chef::Platform::ServiceHelpers
+ unified_mode true
- state_attrs :enabled, :running, :masked
+ provides :service, target_mode: true
+
+ description "Use the **service** resource to manage a service."
default_action :nothing
allowed_actions :enable, :disable, :start, :stop, :restart, :reload,
- :mask, :unmask
-
- def initialize(name, run_context = nil)
- super
- @service_name = name
- @enabled = nil
- @running = nil
- @masked = nil
- @parameters = nil
- @pattern = service_name
- @start_command = nil
- @stop_command = nil
- @status_command = nil
- @restart_command = nil
- @reload_command = nil
- @init_command = nil
- @priority = nil
- @timeout = nil
- @run_levels = nil
- @user = nil
- @supports = { :restart => nil, :reload => nil, :status => nil }
- end
-
- def service_name(arg = nil)
- set_or_return(
- :service_name,
- arg,
- :kind_of => [ String ]
- )
- end
+ :mask, :unmask
+
+ # this is a poor API please do not re-use this pattern
+ property :supports, Hash, default: { restart: nil, reload: nil, status: nil },
+ description: "A list of properties that controls how #{ChefUtils::Dist::Infra::PRODUCT} is to attempt to manage a service: :restart, :reload, :status. For :restart, the init script or other service provider can use a restart command; if :restart is not specified, the #{ChefUtils::Dist::Infra::CLIENT} attempts to stop and then start a service. For :reload, the init script or other service provider can use a reload command. For :status, the init script or other service provider can use a status command to determine if the service is running; if :status is not specified, the #{ChefUtils::Dist::Infra::CLIENT} attempts to match the service_name against the process table as a regular expression, unless a pattern is specified as a parameter property. Default value: { restart: false, reload: false, status: false } for all platforms (except for the Red Hat platform family, which defaults to { restart: false, reload: false, status: true }.)",
+ coerce: proc { |x| x.is_a?(Array) ? x.each_with_object({}) { |i, m| m[i] = true } : x }
+
+ property :service_name, String,
+ description: "An optional property to set the service name if it differs from the resource block's name.",
+ name_property: true
# regex for match against ps -ef when !supports[:has_status] && status == nil
- def pattern(arg = nil)
- set_or_return(
- :pattern,
- arg,
- :kind_of => [ String ]
- )
- end
+ property :pattern, String,
+ description: "The pattern to look for in the process table.",
+ default_description: "The value provided to 'service_name' or the resource block's name",
+ default: lazy { service_name }, desired_state: false
# command to call to start service
- def start_command(arg = nil)
- set_or_return(
- :start_command,
- arg,
- :kind_of => [ String ]
- )
- end
+ property :start_command, [ String, nil, FalseClass ],
+ description: "The command used to start a service.",
+ desired_state: false
# command to call to stop service
- def stop_command(arg = nil)
- set_or_return(
- :stop_command,
- arg,
- :kind_of => [ String ]
- )
- end
+ property :stop_command, [ String, nil, FalseClass ],
+ description: "The command used to stop a service.",
+ desired_state: false
# command to call to get status of service
- def status_command(arg = nil)
- set_or_return(
- :status_command,
- arg,
- :kind_of => [ String ]
- )
- end
+ property :status_command, [ String, nil, FalseClass ],
+ description: "The command used to check the run status for a service.",
+ desired_state: false
# command to call to restart service
- def restart_command(arg = nil)
- set_or_return(
- :restart_command,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def reload_command(arg = nil)
- set_or_return(
- :reload_command,
- arg,
- :kind_of => [ String ]
- )
- end
+ property :restart_command, [ String, nil, FalseClass ],
+ description: "The command used to restart a service.",
+ desired_state: false
+
+ property :reload_command, [ String, nil, FalseClass ],
+ description: "The command used to tell a service to reload its configuration.",
+ desired_state: false
# The path to the init script associated with the service. On many
# distributions this is '/etc/init.d/SERVICE_NAME' by default. In
# non-standard configurations setting this value will save having to
# specify overrides for the start_command, stop_command and
- # restart_command attributes.
- def init_command(arg = nil)
- set_or_return(
- :init_command,
- arg,
- :kind_of => [ String ]
- )
- end
+ # restart_command properties.
+ property :init_command, String,
+ description: "The path to the init script that is associated with the service. Use init_command to prevent the need to specify overrides for the start_command, stop_command, and restart_command properties. When this property is not specified, the #{ChefUtils::Dist::Infra::PRODUCT} will use the default init command for the service provider being used.",
+ desired_state: false
# if the service is enabled or not
- def enabled(arg = nil)
- set_or_return(
- :enabled,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
+ property :enabled, [ TrueClass, FalseClass ], skip_docs: true
# if the service is running or not
- def running(arg = nil)
- set_or_return(
- :running,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
+ property :running, [ TrueClass, FalseClass ], skip_docs: true
# if the service is masked or not
- def masked(arg = nil)
- set_or_return(
- :masked,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
+ property :masked, [ TrueClass, FalseClass ], skip_docs: true
+
+ # if the service is indirect or not
+ property :indirect, [ TrueClass, FalseClass ], skip_docs: true
+
+ property :options, [ Array, String ],
+ description: "Solaris platform only. Options to pass to the service command. See the svcadm manual for details of possible options.",
+ coerce: proc { |x| x.respond_to?(:split) ? x.shellsplit : x }
# Priority arguments can have two forms:
#
@@ -162,56 +111,23 @@ class Chef
# runlevel 2, stopped in 3 with priority 55 and no symlinks or
# similar for other runlevels
#
- def priority(arg = nil)
- set_or_return(
- :priority,
- arg,
- :kind_of => [ Integer, String, Hash ]
- )
- end
-
- # timeout only applies to the windows service manager
- def timeout(arg = nil)
- set_or_return(
- :timeout,
- arg,
- :kind_of => Integer
- )
- end
-
- def parameters(arg = nil)
- set_or_return(
- :parameters,
- arg,
- :kind_of => [ Hash ]
- )
- end
-
- def run_levels(arg = nil)
- set_or_return(
- :run_levels,
- arg,
- :kind_of => [ Array ] )
- end
-
- def user(arg = nil)
- set_or_return(
- :user,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def supports(args = {})
- if args.is_a? Array
- args.each { |arg| @supports[arg] = true }
- elsif args.any?
- @supports = args
- else
- @supports
- end
- end
+ property :priority, [ Integer, String, Hash ],
+ description: "Debian platform only. The relative priority of the program for start and shutdown ordering. May be an integer or a Hash. An integer is used to define the start run levels; stop run levels are then 100-integer. A Hash is used to define values for specific run levels. For example, { 2 => [:start, 20], 3 => [:stop, 55] } will set a priority of twenty for run level two and a priority of fifty-five for run level three."
+
+ property :timeout, Integer,
+ description: "The amount of time (in seconds) to wait before timing out.",
+ default: 900,
+ desired_state: false
+
+ property :parameters, Hash,
+ description: "Upstart only: A hash of parameters to pass to the service command for use in the service definition."
+
+ property :run_levels, Array,
+ description: "RHEL platforms only: Specific run_levels the service will run under."
+ property :user, String,
+ description: "systemd only: A username to run the service under.",
+ introduced: "12.21"
end
end
end
diff --git a/lib/chef/resource/smartos_package.rb b/lib/chef/resource/smartos_package.rb
index 87173ccfa9..08b2f0d82f 100644
--- a/lib/chef/resource/smartos_package.rb
+++ b/lib/chef/resource/smartos_package.rb
@@ -1,6 +1,6 @@
#
# Author:: Toomas Pelberg (<toomasp@gmx.net>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,14 +16,24 @@
# limitations under the License.
#
-require "chef/resource/package"
-require "chef/provider/package/smartos"
+require_relative "package"
class Chef
class Resource
class SmartosPackage < Chef::Resource::Package
- resource_name :smartos_package
- provides :package, os: "solaris2", platform_family: "smartos"
+ unified_mode true
+
+ provides :smartos_package
+ provides :package, platform_family: "smartos"
+
+ description "Use the **smartos_package** resource to manage packages for the SmartOS platform."
+
+ property :package_name, String,
+ description: "An optional property to set the package name if it differs from the resource block's name.",
+ identity: true
+
+ property :version, String,
+ description: "The version of a package to be installed or upgraded."
end
end
end
diff --git a/lib/chef/resource/easy_install_package.rb b/lib/chef/resource/snap_package.rb
index dc5073a6f7..c211b03555 100644
--- a/lib/chef/resource/easy_install_package.rb
+++ b/lib/chef/resource/snap_package.rb
@@ -1,6 +1,6 @@
#
-# Author:: Joe Williams (<joe@joetify.com>)
-# Copyright:: Copyright 2009-2016, Joe Williams
+# Author:: S.Cavallo (<smcavallo@hotmail.com>)
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,17 +16,23 @@
# limitations under the License.
#
-require "chef/resource/package"
+require_relative "package"
class Chef
class Resource
- class EasyInstallPackage < Chef::Resource::Package
- resource_name :easy_install_package
+ class SnapPackage < Chef::Resource::Package
+ unified_mode true
- property :easy_install_binary, String, desired_state: false
- property :python_binary, String, desired_state: false
- property :module_name, String, desired_state: false
+ provides :snap_package
+ description "Use the **snap_package** resource to manage snap packages on Debian and Ubuntu platforms."
+ introduced "15.0"
+
+ property :channel, String,
+ description: "The default channel. For example: stable.",
+ default: "stable",
+ equal_to: %w{edge beta candidate stable},
+ desired_state: false
end
end
end
diff --git a/lib/chef/resource/solaris_package.rb b/lib/chef/resource/solaris_package.rb
index d0f8c144af..381c62a45b 100644
--- a/lib/chef/resource/solaris_package.rb
+++ b/lib/chef/resource/solaris_package.rb
@@ -1,7 +1,7 @@
#
# Author:: Toomas Pelberg (<toomasp@gmx.net>)
# Author:: Prabhu Das (<prabhu.das@clogeny.com>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,15 +17,23 @@
# limitations under the License.
#
-require "chef/resource/package"
-require "chef/provider/package/solaris"
+require_relative "package"
class Chef
class Resource
class SolarisPackage < Chef::Resource::Package
- resource_name :solaris_package
- provides :package, os: "solaris2", platform_family: "nexentacore"
- provides :package, os: "solaris2", platform_family: "solaris2", platform_version: "<= 5.10"
+ unified_mode true
+
+ provides :solaris_package
+
+ description "Use the **solaris_package** resource to manage packages on the Solaris platform."
+
+ property :package_name, String,
+ description: "An optional property to set the package name if it differs from the resource block's name.",
+ identity: true
+
+ property :version, String,
+ description: "The version of a package to be installed or upgraded."
end
end
end
diff --git a/lib/chef/resource/ssh_known_hosts_entry.rb b/lib/chef/resource/ssh_known_hosts_entry.rb
new file mode 100644
index 0000000000..1db811978c
--- /dev/null
+++ b/lib/chef/resource/ssh_known_hosts_entry.rb
@@ -0,0 +1,164 @@
+#
+# Author:: Seth Vargo (<sethvargo@gmail.com>)
+#
+# Copyright:: 2013-2018, Seth Vargo
+# Copyright:: Copyright (c) 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_relative "../resource"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Resource
+ class SshKnownHostsEntry < Chef::Resource
+ unified_mode true
+
+ provides :ssh_known_hosts_entry
+
+ description "Use the **ssh_known_hosts_entry** resource to add an entry for the specified host in /etc/ssh/ssh_known_hosts or a user's known hosts file if specified."
+ introduced "14.3"
+ examples <<~DOC
+ **Add a single entry for github.com with the key auto detected**
+
+ ```ruby
+ ssh_known_hosts_entry 'github.com'
+ ```
+
+ **Add a single entry with your own provided key**
+
+ ```ruby
+ ssh_known_hosts_entry 'github.com' do
+ key 'node.example.com ssh-rsa ...'
+ end
+ ```
+ DOC
+
+ property :host, String,
+ description: "The host to add to the known hosts file.",
+ name_property: true
+
+ property :key, String,
+ description: "An optional key for the host. If not provided this will be automatically determined."
+
+ property :key_type, String,
+ description: "The type of key to store.",
+ default: "rsa"
+
+ property :port, Integer,
+ description: "The server port that the ssh-keyscan command will use to gather the public key.",
+ default: 22
+
+ property :timeout, Integer,
+ description: "The timeout in seconds for ssh-keyscan.",
+ default: 30,
+ desired_state: false
+
+ property :mode, String,
+ description: "The file mode for the ssh_known_hosts file.",
+ default: "0644"
+
+ property :owner, [String, Integer],
+ description: "The file owner for the ssh_known_hosts file.",
+ default: "root"
+
+ property :group, [String, Integer],
+ description: "The file group for the ssh_known_hosts file.",
+ default: lazy { node["root_group"] }
+
+ property :hash_entries, [TrueClass, FalseClass],
+ description: "Hash the hostname and addresses in the ssh_known_hosts file for privacy.",
+ default: false
+
+ property :file_location, String,
+ description: "The location of the ssh known hosts file. Change this to set a known host file for a particular user.",
+ default: "/etc/ssh/ssh_known_hosts"
+
+ action :create do
+ description "Create an entry in the ssh_known_hosts file."
+
+ key =
+ if new_resource.key
+ hoststr = (new_resource.port != 22) ? "[#{new_resource.host}]:#{new_resource.port}" : new_resource.host
+ "#{hoststr} #{type_string(new_resource.key_type)} #{new_resource.key}"
+ else
+ keyscan_cmd = ["ssh-keyscan", "-t#{new_resource.key_type}", "-p #{new_resource.port}"]
+ keyscan_cmd << "-H" if new_resource.hash_entries
+ keyscan_cmd << new_resource.host
+ keyscan = shell_out!(keyscan_cmd.join(" "), timeout: new_resource.timeout)
+ keyscan.stdout
+ end
+
+ key.sub!(/^#{new_resource.host}/, "[#{new_resource.host}]:#{new_resource.port}") if new_resource.port != 22
+
+ comment = key.split("\n").first || ""
+
+ r = with_run_context :root do
+ find_resource(:template, "update ssh known hosts file #{new_resource.file_location}") do
+ source ::File.expand_path("support/ssh_known_hosts.erb", __dir__)
+ local true
+ path new_resource.file_location
+ owner new_resource.owner
+ group new_resource.group
+ mode new_resource.mode
+ action :nothing
+ delayed_action :create
+ backup false
+ variables(entries: [])
+ end
+ end
+
+ keys = r.variables[:entries].reject(&:empty?)
+
+ if key_exists?(keys, key, comment)
+ Chef::Log.debug "Known hosts key for #{new_resource.host} already exists - skipping"
+ else
+ r.variables[:entries].push(key)
+ end
+ end
+
+ # all this does is send an immediate run_action(:create) to the template resource
+ action :flush do
+ description "Immediately flush the entries to the config file. Without this the actual writing of the file is delayed in the #{ChefUtils::Dist::Infra::PRODUCT} run so all entries can be accumulated before writing the file out."
+
+ with_run_context :root do
+ # if you haven't ever called ssh_known_hosts_entry before you're definitely doing it wrong so we blow up hard.
+ find_resource!(:template, "update ssh known hosts file #{new_resource.file_location}").run_action(:create)
+ # it is the user's responsibility to only call this *after* all the ssh_known_hosts_entry resources have been called.
+ # if you call this too early in your run_list you will get a partial known_host file written to disk, and the resource
+ # behavior will not be idempotent (template resources will flap and never show 0 resources updated on converged boxes).
+ Chef::Log.warn "flushed ssh_known_hosts entries to file, later ssh_known_hosts_entry resources will not have been written"
+ end
+ end
+
+ action_class do
+ def key_exists?(keys, key, comment)
+ keys.any? do |line|
+ line.match(/#{Regexp.escape(comment)}|#{Regexp.escape(key)}/)
+ end
+ end
+
+ def type_string(key_type)
+ type_map = {
+ "rsa" => "ssh-rsa",
+ "dsa" => "ssh-dss",
+ "ecdsa" => "ecdsa-sha2-nistp256",
+ "ed25519" => "ssh-ed25519",
+ }
+ type_map[key_type] || key_type
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/subversion.rb b/lib/chef/resource/subversion.rb
deleted file mode 100644
index 9966614eeb..0000000000
--- a/lib/chef/resource/subversion.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@kallistec.com>)
-# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2008-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/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"
- @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/sudo.rb b/lib/chef/resource/sudo.rb
new file mode 100644
index 0000000000..d6587bd441
--- /dev/null
+++ b/lib/chef/resource/sudo.rb
@@ -0,0 +1,267 @@
+#
+# Author:: Bryan W. Berry (<bryan.berry@gmail.com>)
+# Author:: Seth Vargo (<sethvargo@gmail.com>)
+#
+# Copyright:: 2011-2018, Bryan w. Berry
+# Copyright:: 2012-2018, Seth Vargo
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class Sudo < Chef::Resource
+ unified_mode true
+
+ provides(:sudo) { true }
+
+ description "Use the **sudo** resource to add or remove individual sudo entries using sudoers.d files."\
+ " Sudo version 1.7.2 or newer is required to use the sudo resource, as it relies on the"\
+ " `#includedir` directive introduced in version 1.7.2. This resource does not enforce"\
+ " installation of the required sudo version. Chef-supported releases of Ubuntu, SuSE, Debian,"\
+ " and RHEL (6+) all support this feature."
+ introduced "14.0"
+ examples <<~DOC
+ **Grant a user sudo privileges for any command**
+
+ ```ruby
+ sudo 'admin' do
+ user 'admin'
+ end
+ ```
+
+ **Grant a user and groups sudo privileges for any command**
+
+ ```ruby
+ sudo 'admins' do
+ users 'bob'
+ groups 'sysadmins, superusers'
+ end
+ ```
+
+ **Grant passwordless sudo privileges for specific commands**
+
+ ```ruby
+ sudo 'passwordless-access' do
+ commands ['/bin/systemctl restart httpd', '/bin/systemctl restart mysql']
+ nopasswd true
+ end
+ ```
+ DOC
+
+ # According to the sudo man pages sudo will ignore files in an include dir that have a `.` or `~`
+ # We convert either to `__`
+ property :filename, String,
+ description: "The name of the sudoers.d file if it differs from the name of the resource block",
+ name_property: true,
+ coerce: proc { |x| x.gsub(/[\.~]/, "__") }
+
+ property :users, [String, Array],
+ description: "User(s) to provide sudo privileges to. This property accepts either an array or a comma separated list.",
+ default: lazy { [] },
+ coerce: proc { |x| x.is_a?(Array) ? x : x.split(/\s*,\s*/) }
+
+ property :groups, [String, Array],
+ description: "Group(s) to provide sudo privileges to. This property accepts either an array or a comma separated list. Leading % on group names is optional.",
+ default: lazy { [] },
+ coerce: proc { |x| coerce_groups(x) }
+
+ property :commands, Array,
+ description: "An array of full paths to commands this sudoer can execute.",
+ default: ["ALL"]
+
+ property :host, String,
+ description: "The host to set in the sudo configuration.",
+ default: "ALL"
+
+ property :runas, String,
+ description: "User that the command(s) can be run as.",
+ default: "ALL"
+
+ property :nopasswd, [TrueClass, FalseClass],
+ description: "Allow sudo to be run without specifying a password.",
+ default: false
+
+ property :noexec, [TrueClass, FalseClass],
+ description: "Prevent commands from shelling out.",
+ default: false
+
+ property :template, String,
+ description: "The name of the erb template in your cookbook, if you wish to supply your own template."
+
+ property :variables, [Hash, nil],
+ description: "The variables to pass to the custom template. This property is ignored if not using a custom template.",
+ default: nil
+
+ property :defaults, Array,
+ description: "An array of defaults for the user/group.",
+ default: lazy { [] }
+
+ property :command_aliases, Array,
+ description: "Command aliases that can be used as allowed commands later in the configuration.",
+ default: lazy { [] }
+
+ property :setenv, [TrueClass, FalseClass],
+ description: "Determines whether or not to permit preservation of the environment with `sudo -E`.",
+ default: false
+
+ property :env_keep_add, Array,
+ description: "An array of strings to add to `env_keep`.",
+ default: lazy { [] }
+
+ property :env_keep_subtract, Array,
+ description: "An array of strings to remove from `env_keep`.",
+ default: lazy { [] }
+
+ property :visudo_path, String,
+ deprecated: true
+
+ property :visudo_binary, String,
+ description: "The path to visudo for configuration verification.",
+ default: "/usr/sbin/visudo"
+
+ property :config_prefix, String,
+ description: "The directory that contains the sudoers configuration file.",
+ default: lazy { platform_config_prefix }, default_description: "Prefix values based on the node's platform"
+
+ # handle legacy cookbook property
+ def after_created
+ raise "The 'visudo_path' property from the sudo cookbook has been replaced with the 'visudo_binary' property. The path is now more intelligently determined and for most users specifying the path should no longer be necessary. If this resource still cannot determine the path to visudo then provide the absolute path to the binary with the 'visudo_binary' property." if visudo_path
+ end
+
+ # VERY old legacy properties
+ alias_method :user, :users
+ alias_method :group, :groups
+
+ # make sure each group starts with a %
+ def coerce_groups(x)
+ # split strings on the commas with optional spaces on either side
+ groups = x.is_a?(Array) ? x : x.split(/\s*,\s*/)
+
+ # make sure all the groups start with %
+ groups.map { |g| g[0] == "%" ? g : "%#{g}" }
+ end
+
+ # default config prefix paths based on platform
+ # @return [String]
+ def platform_config_prefix
+ case node["platform_family"]
+ when "smartos"
+ "/opt/local/etc"
+ when "mac_os_x"
+ "/private/etc"
+ when "freebsd"
+ "/usr/local/etc"
+ else
+ "/etc"
+ end
+ end
+
+ action :create do
+ description "Create a single sudoers config in the sudoers.d directory"
+
+ validate_properties
+
+ if docker? # don't even put this into resource collection unless we're in docker
+ package "sudo" do
+ not_if "which sudo"
+ end
+ end
+
+ target = "#{new_resource.config_prefix}/sudoers.d/"
+ directory(target)
+
+ Chef::Log.warn("#{new_resource.filename} will be rendered, but will not take effect because the #{new_resource.config_prefix}/sudoers config lacks the includedir directive that loads configs from #{new_resource.config_prefix}/sudoers.d/!") if ::File.readlines("#{new_resource.config_prefix}/sudoers").grep(/includedir/).empty?
+ file_path = "#{target}#{new_resource.filename}"
+
+ if new_resource.template
+ logger.trace("Template property provided, all other properties ignored.")
+
+ template file_path do
+ source new_resource.template
+ mode "0440"
+ variables new_resource.variables
+ verify visudo_content(file_path) if visudo_present?
+ action :create
+ end
+ else
+ template file_path do
+ source ::File.expand_path("support/sudoer.erb", __dir__)
+ local true
+ mode "0440"
+ variables sudoer: (new_resource.groups + new_resource.users).join(","),
+ host: new_resource.host,
+ runas: new_resource.runas,
+ nopasswd: new_resource.nopasswd,
+ noexec: new_resource.noexec,
+ commands: new_resource.commands,
+ command_aliases: new_resource.command_aliases,
+ defaults: new_resource.defaults,
+ setenv: new_resource.setenv,
+ env_keep_add: new_resource.env_keep_add,
+ env_keep_subtract: new_resource.env_keep_subtract
+ verify visudo_content(file_path) if visudo_present?
+ action :create
+ end
+ end
+ end
+
+ action :install do
+ Chef::Log.warn("The sudo :install action has been renamed :create. Please update your cookbook code for the new action")
+ action_create
+ end
+
+ action :remove do
+ Chef::Log.warn("The sudo :remove action has been renamed :delete. Please update your cookbook code for the new action")
+ action_delete
+ end
+
+ # Removes a user from the sudoers group
+ action :delete do
+ description "Remove a sudoers config from the sudoers.d directory"
+
+ file "#{new_resource.config_prefix}/sudoers.d/#{new_resource.filename}" do
+ action :delete
+ end
+ end
+
+ action_class do
+ # Ensure that the inputs are valid (we cannot just use the resource for this)
+ def validate_properties
+ # if group, user, env_keep_add, env_keep_subtract and template are nil, throw an exception
+ raise "You must specify users, groups, env_keep_add, env_keep_subtract, or template properties!" if new_resource.users.empty? && new_resource.groups.empty? && new_resource.template.nil? && new_resource.env_keep_add.empty? && new_resource.env_keep_subtract.empty?
+
+ # if specifying user or group and template at the same time fail
+ raise "You cannot specify users or groups properties and also specify a template. To use your own template pass in all template variables using the variables property." if (!new_resource.users.empty? || !new_resource.groups.empty?) && !new_resource.template.nil?
+ end
+
+ def visudo_present?
+ return true if ::File.exist?(new_resource.visudo_binary)
+
+ Chef::Log.warn("The visudo binary cannot be found at '#{new_resource.visudo_binary}'. Skipping sudoer file validation. If visudo is on this system you can specify the path using the 'visudo_binary' property.")
+ end
+
+ def visudo_content(path)
+ if ::File.exist?(path)
+ "cat #{new_resource.config_prefix}/sudoers | #{new_resource.visudo_binary} -cf - && #{new_resource.visudo_binary} -cf %{path}"
+ else
+ "cat #{new_resource.config_prefix}/sudoers %{path} | #{new_resource.visudo_binary} -cf -"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/support/client.erb b/lib/chef/resource/support/client.erb
new file mode 100644
index 0000000000..6507c74440
--- /dev/null
+++ b/lib/chef/resource/support/client.erb
@@ -0,0 +1,64 @@
+<% %w(@node_name
+ @chef_license
+ @chef_server_url
+ @event_loggers
+ @file_backup_path
+ @file_cache_path
+ @file_staging_uses_destdir
+ @formatters
+ @http_proxy
+ @https_proxy
+ @ftp_proxy
+ @log_level
+ @minimal_ohai
+ @named_run_list
+ @no_proxy
+ @ohai_disabled_plugins
+ @ohai_optional_plugins
+ @pid_file
+ @policy_group
+ @policy_name
+ @ssl_verify_mode).each do |prop| -%>
+<% next if instance_variable_get(prop).nil? || instance_variable_get(prop).empty? -%>
+<%=prop.delete_prefix("@") %> <%= instance_variable_get(prop).inspect %>
+<% end -%>
+<%# log_location is special due to STDOUT/STDERR from String -> IO Object -%>
+<% unless @log_location.nil? %>
+ <% if @log_location.is_a?(String) && %w(STDOUT STDERR).include?(@log_location) -%>
+log_location <%= @log_location %>
+ <% else -%>
+log_location <%= @log_location.inspect %>
+ <% end -%>
+<% end -%>
+<%# The code below is not DRY on purpose to improve readability -%>
+<% unless @start_handlers.empty? -%>
+ # Do not crash if a start handler is missing / not installed yet
+ begin
+ <% @start_handlers.each do |handler| -%>
+ start_handlers << <%= @handler %>
+ <% end -%>
+ rescue NameError => e
+ Chef::Log.error e
+ end
+<% end -%>
+<% unless @report_handlers.empty? -%>
+ # Do not crash if a report handler is missing / not installed yet
+ begin
+ <% @report_handlers.each do |handler| -%>
+ report_handlers << <%= @handler %>
+ <% end -%>
+ rescue NameError => e
+ Chef::Log.error e
+ end
+<% end -%>
+<% unless @exception_handlers.empty? -%>
+ # Do not crash if an exception handler is missing / not installed yet
+ begin
+ <% @exception_handlers.each do |handler| -%>
+ exception_handlers << <%= @handler %>
+ <% end -%>
+ rescue NameError => e
+ Chef::Log.error e
+ end
+<% end -%>
+<%= @additional_config -%>
diff --git a/lib/chef/resource/support/cron.d.erb b/lib/chef/resource/support/cron.d.erb
new file mode 100644
index 0000000000..579e64f405
--- /dev/null
+++ b/lib/chef/resource/support/cron.d.erb
@@ -0,0 +1,28 @@
+# Generated by <%= ChefUtils::Dist::Infra::PRODUCT %>. Changes will be overwritten.
+<% if @mailto -%>
+MAILTO=<%= @mailto %>
+<% end -%>
+<% if @path -%>
+PATH=<%= @path %>
+<% end -%>
+<% if @shell -%>
+SHELL=<%= @shell %>
+<% end -%>
+<% if @home -%>
+HOME=<%= @home %>
+<% end -%>
+<% if @random_delay -%>
+RANDOM_DELAY=<%= @random_delay %>
+<% end -%>
+<% @environment.each do |key, val| -%>
+<%= key %>=<%= val.to_s.shellescape %>
+<% end -%>
+
+<% if @comment -%>
+# <%= @comment %>
+<% end -%>
+<% if @predefined_value -%>
+<%= @predefined_value %> <%= @user %> <%= @command %>
+<% else -%>
+<%= @minute %> <%= @hour %> <%= @day %> <%= @month %> <%= @weekday %> <%= @user %> <%= @command %>
+<% end -%>
diff --git a/lib/chef/resource/support/cron_access.erb b/lib/chef/resource/support/cron_access.erb
new file mode 100644
index 0000000000..1f4e74ab23
--- /dev/null
+++ b/lib/chef/resource/support/cron_access.erb
@@ -0,0 +1,4 @@
+# Generated by <%= ChefUtils::Dist::Infra::PRODUCT %>. Changes will be overwritten.
+<% @users.sort.uniq.each do |user| -%>
+<%= user %>
+<% end -%>
diff --git a/lib/chef/resource/support/ssh_known_hosts.erb b/lib/chef/resource/support/ssh_known_hosts.erb
new file mode 100644
index 0000000000..0073b250ff
--- /dev/null
+++ b/lib/chef/resource/support/ssh_known_hosts.erb
@@ -0,0 +1,3 @@
+<% @entries.sort.each do |entry| -%>
+<%= entry %>
+<% end -%>
diff --git a/lib/chef/resource/support/sudoer.erb b/lib/chef/resource/support/sudoer.erb
new file mode 100644
index 0000000000..f8c9760d94
--- /dev/null
+++ b/lib/chef/resource/support/sudoer.erb
@@ -0,0 +1,17 @@
+# This file is managed by <%= ChefUtils::Dist::Infra::PRODUCT %>. Changes will be overwritten.
+
+<% @command_aliases.each do |a| -%>
+Cmnd_Alias <%= a[:name].upcase %> = <%= a[:command_list].join(', ') %>
+<% end -%>
+<% @env_keep_add.each do |env_keep| -%>
+Defaults env_keep += "<%= env_keep %>"
+<% end -%>
+<% @env_keep_subtract.each do |env_keep| -%>
+Defaults env_keep -= "<%= env_keep %>"
+<% end -%>
+<% @commands.each do |command| -%>
+<% unless @sudoer.empty? %><%= @sudoer %> <%= @host %>=(<%= @runas %>) <%= 'NOEXEC:' if @noexec %><%= 'NOPASSWD:' if @nopasswd.to_s == 'true' %><%= 'SETENV:' if @setenv.to_s == 'true' %><%= command %><% end -%>
+<% end -%>
+<% unless @defaults.empty? %>
+Defaults:<%= @sudoer %> <%= @defaults.join(',') %>
+<% end -%>
diff --git a/lib/chef/resource/support/ulimit.erb b/lib/chef/resource/support/ulimit.erb
new file mode 100644
index 0000000000..25ac0fde5c
--- /dev/null
+++ b/lib/chef/resource/support/ulimit.erb
@@ -0,0 +1,41 @@
+# Generated by <%= ChefUtils::Dist::Infra::PRODUCT %>. Changes will be overwritten.
+
+# Limits settings for <%= @ulimit_user %>
+
+<% unless @filehandle_limit.nil? -%>
+<%= @ulimit_user -%> - nofile <%= @filehandle_limit %>
+<% else -%><% unless @filehandle_soft_limit.nil? -%><%= @ulimit_user -%> soft nofile <%= @filehandle_soft_limit %><% end -%>
+<% unless @filehandle_hard_limit.nil? -%><%= @ulimit_user -%> hard nofile <%= @filehandle_hard_limit %><% end -%>
+<% end -%>
+
+<% unless @process_limit.nil? -%>
+<%= @ulimit_user -%> - nproc <%= @process_limit %>
+<% else -%><% unless @process_soft_limit.nil? -%><%= @ulimit_user -%> soft nproc <%= @process_soft_limit %><% end -%>
+<% unless @process_hard_limit.nil? -%><%= @ulimit_user -%> hard nproc <%= @process_hard_limit %><% end -%>
+<% end -%>
+
+<% unless @memory_limit.nil? -%>
+<%= @ulimit_user -%> - memlock <%= @memory_limit %>
+<% end -%>
+
+<% unless @core_limit.nil? -%>
+<%= @ulimit_user -%> - core <%= @core_limit %>
+<% else -%><% unless @core_soft_limit.nil? -%><%= @ulimit_user -%> soft core <%= @core_soft_limit %><% end -%>
+<% unless @core_hard_limit.nil? -%><%= @ulimit_user -%> hard core <%= @core_hard_limit %><% end -%>
+<% end -%>
+
+<% unless @stack_limit.nil? -%>
+<%= @ulimit_user -%> - stack <%= @stack_limit %>
+<% else -%><% unless @stack_soft_limit.nil? -%><%= @ulimit_user -%> soft stack <%= @stack_soft_limit %><% end -%>
+<% unless @stack_hard_limit.nil? -%><%= @ulimit_user -%> hard stack <%= @stack_hard_limit %><% end -%>
+<% end -%>
+
+<% unless @rtprio_limit.nil? -%>
+<%= @ulimit_user -%> - rtprio <%= @rtprio_limit %>
+<% else -%><% unless @rtprio_soft_limit.nil? -%><%= @ulimit_user -%> soft rtprio <%= @rtprio_soft_limit %><% end -%>
+<% unless @rtprio_hard_limit.nil? -%><%= @ulimit_user -%> hard rtprio <%= @rtprio_hard_limit %><% end -%>
+<% end -%>
+
+<% unless @virt_limit.nil? -%>
+ <%= @ulimit_user -%> - as <%= @virt_limit %>
+<% end -%>
diff --git a/lib/chef/resource/swap_file.rb b/lib/chef/resource/swap_file.rb
new file mode 100644
index 0000000000..3d8f31de48
--- /dev/null
+++ b/lib/chef/resource/swap_file.rb
@@ -0,0 +1,228 @@
+#
+# Copyright:: 2012-2018, Seth Vargo
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class SwapFile < Chef::Resource
+ unified_mode true
+
+ provides(:swap_file) { true }
+
+ description "Use the **swap_file** resource to create or delete swap files on Linux systems, and optionally to manage the swappiness configuration for a host."
+ introduced "14.0"
+ examples <<~DOC
+ **Create a swap file**
+
+ ```ruby
+ swap_file '/dev/sda1' do
+ size 1024
+ end
+ ```
+
+ **Remove a swap file**
+
+ ```ruby
+ swap_file '/dev/sda1' do
+ action :remove
+ end
+ ```
+ DOC
+
+ property :path, String,
+ description: "The path where the swap file will be created on the system if it differs from the resource block's name.",
+ name_property: true
+
+ property :size, Integer,
+ description: "The size (in MBs) of the swap file."
+
+ property :persist, [TrueClass, FalseClass],
+ description: "Persist the swapon.",
+ default: false
+
+ property :timeout, Integer,
+ description: "Timeout for `dd` / `fallocate` commands.",
+ default: 600,
+ desired_state: false
+
+ property :swappiness, Integer,
+ description: "The swappiness value to set on the system."
+
+ action :create do
+ description "Create a swapfile."
+
+ if swap_enabled?
+ Chef::Log.debug("#{new_resource} already created - nothing to do")
+ else
+ begin
+ Chef::Log.info "starting first create: #{node["virtualization"]["system"]}"
+ do_create(swap_creation_command)
+ rescue Mixlib::ShellOut::ShellCommandFailed => e
+ Chef::Log.warn("#{new_resource} Rescuing failed swapfile creation for #{new_resource.path}")
+ Chef::Log.debug("#{new_resource} Exception when creating swapfile #{new_resource.path}: #{e}")
+ do_create(dd_command)
+ end
+ end
+ if new_resource.swappiness
+ sysctl "vm.swappiness" do
+ value new_resource.swappiness
+ end
+ end
+ end
+
+ action :remove do
+ description "Remove a swapfile and disable swap."
+
+ swapoff if swap_enabled?
+ remove_swapfile if ::File.exist?(new_resource.path)
+ end
+
+ action_class do
+ def do_create(command)
+ create_swapfile(command)
+ set_permissions
+ mkswap
+ swapon
+ persist if persist?
+ end
+
+ def create_swapfile(command)
+ converge_by "create empty swapfile at #{new_resource.path}" do # ~FC054
+ shell_out!(command, timeout: new_resource.timeout)
+ end
+ end
+
+ def set_permissions
+ permissions = "600"
+ converge_by "set permissions on #{new_resource.path} to #{permissions}" do
+ shell_out!("chmod #{permissions} #{new_resource.path}")
+ end
+ end
+
+ def mkswap
+ converge_by "make #{new_resource.path} swappable" do
+ shell_out!("mkswap -f #{new_resource.path}")
+ end
+ end
+
+ def swapon
+ converge_by "enable swap for #{new_resource.path}" do
+ shell_out!("swapon #{new_resource.path}")
+ end
+ end
+
+ def swapoff
+ converge_by "turn off swap for #{new_resource.path}" do
+ shell_out!("swapoff #{new_resource.path}")
+ end
+ end
+
+ def remove_swapfile
+ converge_by "remove swap file #{new_resource.path}" do
+ ::FileUtils.rm(new_resource.path)
+ end
+ end
+
+ def swap_enabled?
+ enabled_swapfiles = shell_out("swapon --summary").stdout
+ # Regex for our resource path and only our resource path
+ # It will terminate on whitespace after the path it match
+ # /testswapfile would match
+ # /testswapfiledir/someotherfile will not
+ swapfile_regex = Regexp.new("^#{new_resource.path}[\\s\\t\\n\\f]+")
+ !swapfile_regex.match(enabled_swapfiles).nil?
+ end
+
+ def swap_creation_command
+ command = if compatible_filesystem? && compatible_kernel && !docker?
+ fallocate_command
+ else
+ dd_command
+ end
+ Chef::Log.debug("#{new_resource} swap creation command is '#{command}'")
+ command
+ end
+
+ def fallback_swap_creation_command
+ command = dd_command
+ Chef::Log.debug("#{new_resource} fallback swap creation command is '#{command}'")
+ command
+ end
+
+ # The block size (1MB)
+ def block_size
+ 1_048_576
+ end
+
+ def fallocate_size
+ size = block_size * new_resource.size
+ Chef::Log.debug("#{new_resource} fallocate size is #{size}")
+ size
+ end
+
+ def fallocate_command
+ size = fallocate_size
+ command = "fallocate -l #{size} #{new_resource.path}"
+ Chef::Log.debug("#{new_resource} fallocate command is '#{command}'")
+ command
+ end
+
+ def dd_command
+ command = "dd if=/dev/zero of=#{new_resource.path} bs=#{block_size} count=#{new_resource.size}"
+ Chef::Log.debug("#{new_resource} dd command is '#{command}'")
+ command
+ end
+
+ def compatible_kernel
+ fallocate_location = shell_out("which fallocate").stdout
+ Chef::Log.debug("#{new_resource} fallocate location is '#{fallocate_location}'")
+ ::File.exist?(fallocate_location.chomp)
+ end
+
+ def compatible_filesystem?
+ compatible_filesystems = %w{xfs ext4}
+ parent_directory = ::File.dirname(new_resource.path)
+ # Get FS info, get second line as first is column headings
+ command = "df -PT #{parent_directory} | awk 'NR==2 {print $2}'"
+ result = shell_out(command).stdout
+ Chef::Log.debug("#{new_resource} filesystem listing is '#{result}'")
+ compatible_filesystems.any? { |fs| result.include? fs }
+ end
+
+ def persist?
+ !!new_resource.persist
+ end
+
+ def persist
+ fstab = "/etc/fstab"
+ contents = ::File.readlines(fstab)
+ addition = "#{new_resource.path} swap swap defaults 0 0"
+
+ if contents.any? { |line| line.strip == addition }
+ Chef::Log.debug("#{new_resource} already added to /etc/fstab - skipping")
+ else
+ Chef::Log.info("#{new_resource} adding entry to #{fstab} for #{new_resource.path}")
+
+ contents << "#{addition}\n"
+ ::File.open(fstab, "w") { |f| f.write(contents.join("")) }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/sysctl.rb b/lib/chef/resource/sysctl.rb
new file mode 100644
index 0000000000..bf9424f35f
--- /dev/null
+++ b/lib/chef/resource/sysctl.rb
@@ -0,0 +1,233 @@
+#
+# Copyright:: 2018, Webb Agile Solutions Ltd.
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class Sysctl < Chef::Resource
+ unified_mode true
+
+ provides(:sysctl) { true }
+ provides(:sysctl_param) { true }
+
+ description "Use the **sysctl** resource to set or remove kernel parameters using the `sysctl` command line tool and configuration files in the system's `sysctl.d` directory. Configuration files managed by this resource are named `99-chef-KEYNAME.conf`."
+ examples <<~DOC
+ **Set vm.swappiness**:
+
+ ```ruby
+ sysctl 'vm.swappiness' do
+ value 19
+ end
+ ```
+
+ **Remove kernel.msgmax**:
+
+ **Note**: This only removes the sysctl.d config for kernel.msgmax. The value will be set back to the kernel default value.
+
+ ```ruby
+ sysctl 'kernel.msgmax' do
+ action :remove
+ end
+ ```
+
+ **Adding Comments to sysctl configuration files**:
+
+ ```ruby
+ sysctl 'vm.swappiness' do
+ value 19
+ comment "define how aggressively the kernel will swap memory pages."
+ end
+ ```
+
+ This produces /etc/sysctl.d/99-chef-vm.swappiness.conf as follows:
+
+ ```
+ # define how aggressively the kernel will swap memory pages.
+ vm.swappiness = 1
+ ```
+
+ **Converting sysctl settings from shell scripts**:
+
+ Example of existing settings:
+
+ ```bash
+ fs.aio-max-nr = 1048576 net.ipv4.ip_local_port_range = 9000 65500 kernel.sem = 250 32000 100 128
+ ```
+
+ Converted to sysctl resources:
+
+ ```ruby
+ sysctl 'fs.aio-max-nr' do
+ value '1048576'
+ end
+
+ sysctl 'net.ipv4.ip_local_port_range' do
+ value '9000 65500'
+ end
+
+ sysctl 'kernel.sem' do
+ value '250 32000 100 128'
+ end
+ ```
+ DOC
+
+ introduced "14.0"
+
+ property :key, String,
+ description: "The kernel parameter key in dotted format if it differs from the resource block's name.",
+ name_property: true
+
+ property :ignore_error, [TrueClass, FalseClass],
+ description: "Ignore any errors when setting the value on the command line.",
+ default: false, desired_state: false
+
+ property :value, [Array, String, Integer, Float],
+ description: "The value to set.",
+ coerce: proc { |v| coerce_value(v) },
+ required: [:apply]
+
+ property :comment, [Array, String],
+ description: "Comments, placed above the resource setting in the generated file. For multi-line comments, use an array of strings, one per line.",
+ default: [],
+ introduced: "15.8"
+
+ property :conf_dir, String,
+ description: "The configuration directory to write the config to.",
+ default: "/etc/sysctl.d"
+
+ def after_created
+ raise "The sysctl resource requires Linux as it needs sysctl and the sysctl.d directory functionality." unless node["os"] == "linux"
+ end
+
+ def coerce_value(v)
+ case v
+ when Array
+ v.join(" ")
+ else
+ v.to_s
+ end
+ end
+
+ load_current_value do
+
+ value get_sysctl_value(key)
+ rescue
+ current_value_does_not_exist!
+
+ end
+
+ action :apply do
+ description "Apply a sysctl value."
+
+ converge_if_changed do
+ # set it temporarily
+ set_sysctl_param(new_resource.key, new_resource.value)
+
+ directory new_resource.conf_dir
+
+ file "#{new_resource.conf_dir}/99-chef-#{new_resource.key.tr("/", ".")}.conf" do
+ content contruct_sysctl_content
+ end
+
+ execute "Load sysctl values" do
+ command "sysctl #{"-e " if new_resource.ignore_error}-p"
+ default_env true
+ action :run
+ end
+ end
+ end
+
+ action :remove do
+ description "Remove a sysctl value."
+
+ # only converge the resource if the file actually exists to delete
+ if ::File.exist?("#{new_resource.conf_dir}/99-chef-#{new_resource.key.tr("/", ".")}.conf")
+ converge_by "removing sysctl config at #{new_resource.conf_dir}/99-chef-#{new_resource.key.tr("/", ".")}.conf" do
+ file "#{new_resource.conf_dir}/99-chef-#{new_resource.key.tr("/", ".")}.conf" do
+ action :delete
+ end
+
+ execute "Load sysctl values" do
+ default_env true
+ command "sysctl -p"
+ action :run
+ end
+ end
+ end
+ end
+
+ action_class do
+ #
+ # Shell out to set the sysctl value
+ #
+ # @param [String] key The sysctl key
+ # @param [String] value The value of the sysctl key
+ #
+ def set_sysctl_param(key, value)
+ shell_out!("sysctl #{"-e " if new_resource.ignore_error}-w \"#{key}=#{value}\"")
+ end
+
+ #
+ # construct a string, joining members of new_resource.comment and new_resource.value
+ #
+ # @return [String] The text file content
+ #
+ def contruct_sysctl_content
+ sysctl_lines = Array(new_resource.comment).map { |c| "# #{c.strip}" }
+
+ sysctl_lines << "#{new_resource.key} = #{new_resource.value}"
+
+ sysctl_lines.join("\n")
+ end
+ end
+
+ private
+
+ # shellout to sysctl to get the current value
+ # ignore missing keys by using '-e'
+ # convert tabs to spaces since sysctl tab deliminates multivalue parameters
+ # strip the newline off the end of the output as well
+ #
+ # Chef creates a file in sysctld with parameter configuration
+ # Thus this config will persists even after rebooting the system
+ # User can be in a half configured state, where he has already updated the value
+ # which he wants to be configured from the resource
+ # Therefore we need an extra check with sysctld to ensure a correct idempotency
+ #
+ def get_sysctl_value(key)
+ val = shell_out!("sysctl -n -e #{key}").stdout.tr("\t", " ").strip
+ raise unless val == get_sysctld_value(key)
+
+ val
+ end
+
+ # Check if chef has already configured a value for the given key and
+ # return the value. Raise in case this conf file needs to be created
+ # or updated
+ def get_sysctld_value(key)
+ raise unless ::File.exist?("/etc/sysctl.d/99-chef-#{key.tr("/", ".")}.conf")
+
+ k, v = ::File.read("/etc/sysctl.d/99-chef-#{key.tr("/", ".")}.conf").match(/(.*) = (.*)/).captures
+ raise "Unknown sysctl key!" if k.nil?
+ raise "Unknown sysctl value!" if v.nil?
+
+ v
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/systemd_unit.rb b/lib/chef/resource/systemd_unit.rb
index 688f2e9dcd..b028214441 100644
--- a/lib/chef/resource/systemd_unit.rb
+++ b/lib/chef/resource/systemd_unit.rb
@@ -1,6 +1,6 @@
#
# Author:: Nathan Williams (<nath.e.will@gmail.com>)
-# Copyright:: Copyright 2016, Nathan Williams
+# Copyright:: Copyright 2016-2018, Nathan Williams
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,31 +16,97 @@
# limitations under the License.
#
-require "chef/resource"
+require_relative "../resource"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
require "iniparse"
class Chef
class Resource
class SystemdUnit < Chef::Resource
- resource_name :systemd_unit
+ unified_mode true
+
+ provides(:systemd_unit) { true }
+
+ description "Use the **systemd_unit** resource to create, manage, and run [systemd units](https://www.freedesktop.org/software/systemd/man/systemd.html#Concepts)."
+ introduced "12.11"
+ examples <<~DOC
+ **Create systemd service unit file from a Hash**
+
+ ```ruby
+ systemd_unit 'etcd.service' do
+ content({Unit: {
+ Description: 'Etcd',
+ Documentation: ['https://coreos.com/etcd', 'man:etcd(1)'],
+ After: 'network.target',
+ },
+ Service: {
+ Type: 'notify',
+ ExecStart: '/usr/local/etcd',
+ Restart: 'always',
+ },
+ Install: {
+ WantedBy: 'multi-user.target',
+ }})
+ action [:create, :enable]
+ end
+ ```
+
+ **Create systemd service unit file from a String**
+
+ ```ruby
+ systemd_unit 'sysstat-collect.timer' do
+ content <<~EOU
+ [Unit]
+ Description=Run system activity accounting tool every 10 minutes
+
+ [Timer]
+ OnCalendar=*:00/10
+
+ [Install]
+ WantedBy=sysstat.service
+ EOU
+
+ action [:create, :enable]
+ end
+ ```
+ DOC
default_action :nothing
allowed_actions :create, :delete,
- :enable, :disable,
- :mask, :unmask,
- :start, :stop,
- :restart, :reload,
- :try_restart, :reload_or_restart,
- :reload_or_try_restart
-
- property :enabled, [TrueClass, FalseClass]
- property :active, [TrueClass, FalseClass]
- property :masked, [TrueClass, FalseClass]
- property :static, [TrueClass, FalseClass]
- property :user, String, desired_state: false
- property :content, [String, Hash]
+ :preset, :revert,
+ :enable, :disable, :reenable,
+ :mask, :unmask,
+ :start, :stop,
+ :restart, :reload,
+ :try_restart, :reload_or_restart,
+ :reload_or_try_restart
+
+ # Internal provider-managed properties
+ property :enabled, [TrueClass, FalseClass], skip_docs: true
+ property :active, [TrueClass, FalseClass], skip_docs: true
+ property :masked, [TrueClass, FalseClass], skip_docs: true
+ property :static, [TrueClass, FalseClass], skip_docs: true
+ property :indirect, [TrueClass, FalseClass], skip_docs: true
+
+ # User-provided properties
+ property :user, String, desired_state: false,
+ description: "The user account that the systemd unit process is run under. The path to the unit for that user would be something like '/etc/systemd/user/sshd.service'. If no user account is specified, the systemd unit will run under a 'system' account, with the path to the unit being something like '/etc/systemd/system/sshd.service'."
+
+ property :content, [String, Hash],
+ description: "A string or hash that contains a systemd [unit file](https://www.freedesktop.org/software/systemd/man/systemd.unit.html) definition that describes the properties of systemd-managed entities, such as services, sockets, devices, and so on. In #{ChefUtils::Dist::Infra::PRODUCT} 14.4 or later, repeatable options can be implemented with an array."
+
property :triggers_reload, [TrueClass, FalseClass],
- default: true, desired_state: false
+ description: "Specifies whether to trigger a daemon reload when creating or deleting a unit.",
+ default: true, desired_state: false
+
+ property :verify, [TrueClass, FalseClass],
+ default: true, desired_state: false,
+ description: "Specifies if the unit will be verified before installation. Systemd can be overly strict when verifying units, so in certain cases it is preferable not to verify the unit."
+
+ property :unit_name, String, desired_state: false,
+ name_property: true,
+ description: "The name of the unit file if it differs from the resource block's name.",
+ introduced: "13.7"
def to_ini
case content
@@ -49,7 +115,9 @@ class Chef
content.each_pair do |sect, opts|
doc.section(sect) do |section|
opts.each_pair do |opt, val|
- section.option(opt, val)
+ [val].flatten.each do |v|
+ section.option(opt, v)
+ end
end
end
end
diff --git a/lib/chef/resource/template.rb b/lib/chef/resource/template.rb
index 896aa71340..e3f266740d 100644
--- a/lib/chef/resource/template.rb
+++ b/lib/chef/resource/template.rb
@@ -2,7 +2,7 @@
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Seth Chisamore (<schisamo@chef.io>)
# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,13 +18,26 @@
# limitations under the License.
#
-require "chef/resource/file"
-require "chef/provider/template"
-require "chef/mixin/securable"
+require_relative "file"
+require_relative "../mixin/securable"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Resource
+ # A cookbook template is an Embedded Ruby (ERB) template that is used to dynamically generate static text files.
+ # Templates may contain Ruby expressions and statements, and are a great way to manage configuration files. Use the
+ # template resource to add cookbook templates to recipes; place the corresponding Embedded Ruby (ERB) template file
+ # in a cookbook's /templates directory.
+ #
+ # Use the template resource to manage the contents of a file using an Embedded Ruby (ERB) template by transferring
+ # files from a sub-directory of COOKBOOK_NAME/templates/ to a specified path located on a host that is running the
+ # chef-client. This resource includes actions and properties from the file resource. Template files managed by the
+ # template resource follow the same file specificity rules as the remote_file and file resources.
class Template < Chef::Resource::File
+ unified_mode true
+
+ provides :template
+
include Chef::Mixin::Securable
attr_reader :inline_helper_blocks
@@ -33,9 +46,6 @@ class Chef
def initialize(name, run_context = nil)
super
@source = "#{::File.basename(name)}.erb"
- @cookbook = nil
- @local = false
- @variables = Hash.new
@inline_helper_blocks = {}
@inline_helper_modules = []
@helper_modules = []
@@ -45,33 +55,21 @@ class Chef
set_or_return(
:source,
file,
- :kind_of => [ String, Array ]
+ kind_of: [ String, Array ]
)
end
- def variables(args = nil)
- set_or_return(
- :variables,
- args,
- :kind_of => [ Hash ]
- )
- end
+ property :variables, Hash,
+ description: "The variables property of the template resource can be used to reference a partial template file by using a Hash.",
+ default: lazy { {} }
- def cookbook(args = nil)
- set_or_return(
- :cookbook,
- args,
- :kind_of => [ String ]
- )
- end
+ property :cookbook, String,
+ description: "The cookbook in which a file is located (if it is not located in the current cookbook). The default value is the current cookbook.",
+ desired_state: false
- def local(args = nil)
- set_or_return(
- :local,
- args,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
+ property :local, [ TrueClass, FalseClass ],
+ default: false, desired_state: false,
+ description: "Load a template from a local path. By default, the #{ChefUtils::Dist::Infra::CLIENT} loads templates from a cookbook's /templates directory. When this property is set to true, use the source property to specify the path to a template on the local node."
# Declares a helper method to be defined in the template context when
# rendering.
@@ -112,7 +110,7 @@ class Chef
"`helper(:method)` requires a block argument (e.g., `helper(:method) { code }`)"
end
- unless method_name.kind_of?(Symbol)
+ unless method_name.is_a?(Symbol)
raise Exceptions::ValidationFailed,
"method_name argument to `helper(method_name)` must be a symbol (e.g., `helper(:method) { code }`)"
end
@@ -166,13 +164,13 @@ class Chef
"Passing both a module and block to #helpers is not supported. Call #helpers multiple times instead"
elsif block_given?
@inline_helper_modules << block
- elsif module_name.kind_of?(::Module)
+ elsif module_name.is_a?(::Module)
@helper_modules << module_name
elsif module_name.nil?
raise Exceptions::ValidationFailed,
"#helpers requires either a module name or inline module code as a block.\n" +
- "e.g.: helpers do; helper_code; end;\n" +
- "OR: helpers(MyHelpersModule)"
+ "e.g.: helpers do; helper_code; end;\n" +
+ "OR: helpers(MyHelpersModule)"
else
raise Exceptions::ValidationFailed,
"Argument to #helpers must be a module. You gave #{module_name.inspect} (#{module_name.class})"
diff --git a/lib/chef/resource/timezone.rb b/lib/chef/resource/timezone.rb
new file mode 100644
index 0000000000..04e5884b88
--- /dev/null
+++ b/lib/chef/resource/timezone.rb
@@ -0,0 +1,178 @@
+#
+# Author:: Kirill Kouznetsov <agon.smith@gmail.com>
+#
+# Copyright:: 2018, Kirill Kouznetsov.
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class Timezone < Chef::Resource
+ unified_mode true
+
+ provides :timezone
+
+ description "Use the **timezone** resource to change the system timezone on Windows, Linux, and macOS hosts. Timezones are specified in tz database format, with a complete list of available TZ values for Linux and macOS here: <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>. On Windows systems run `tzutil /l` for a complete list of valid timezones."
+ introduced "14.6"
+ examples <<~DOC
+ **Set the timezone to UTC**
+
+ ```ruby
+ timezone 'UTC'
+ ```
+
+ **Set the timezone to America/Los_Angeles with a friendly resource name on Linux/macOS**
+
+ ```ruby
+ timezone 'Set the host's timezone to America/Los_Angeles' do
+ timezone 'America/Los_Angeles'
+ end
+ ```
+
+ **Set the timezone to PST with a friendly resource name on Windows**
+
+ ```ruby
+ timezone 'Set the host's timezone to PST' do
+ timezone 'Pacific Standard time'
+ end
+ ```
+ DOC
+
+ property :timezone, String,
+ description: "An optional property to set the timezone value if it differs from the resource block's name.",
+ name_property: true
+
+ # detect the current TZ on darwin hosts
+ #
+ # @since 14.7
+ # @return [String] TZ database value
+ def current_macos_tz
+ tz_shellout = shell_out!(["systemsetup", "-gettimezone"])
+ if /You need administrator access/.match?(tz_shellout.stdout)
+ raise "The timezone resource requires administrative privileges to run on macOS hosts!"
+ else
+ /Time Zone: (.*)/.match(tz_shellout.stdout)[1]
+ end
+ end
+
+ # detect the current timezone on windows hosts
+ #
+ # @since 14.7
+ # @return [String] timezone id
+ def current_windows_tz
+ tz_shellout = shell_out("tzutil /g")
+ raise "There was an error running the tzutil command" if tz_shellout.error?
+
+ tz_shellout.stdout.strip
+ end
+
+ # detect the current timezone on systemd hosts
+ #
+ # @since 16.5
+ # @return [String] timezone id
+ def current_systemd_tz
+ tz_shellout = shell_out(["/usr/bin/timedatectl", "status"])
+ raise "There was an error running the timedatectl command" if tz_shellout.error?
+
+ # https://rubular.com/r/eV68MX9XXbyG4k
+ /Time zone: (.*) \(.*/.match(tz_shellout.stdout)[1]
+ end
+
+ # detect the current timezone on non-systemd RHEL-ish hosts
+ #
+ # @since 16.5
+ # @return [String] timezone id
+ def current_rhel_tz
+ return nil unless ::File.exist?("/etc/sysconfig/clock")
+
+ # https://rubular.com/r/aoj01L3bKBM7wh
+ /ZONE="(.*)"/.match(::File.read("/etc/sysconfig/clock"))[1]
+ end
+
+ load_current_value do
+ if systemd?
+ timezone current_systemd_tz
+ else
+ case node["platform_family"]
+ # Old version of RHEL < 7 and Amazon 201X
+ when "rhel", "amazon"
+ timezone current_rhel_tz
+ when "mac_os_x"
+ timezone current_macos_tz
+ when "windows"
+ timezone current_windows_tz
+ end
+ end
+ end
+
+ action :set do
+ description "Set the timezone."
+
+ # we have to check windows first since the value isn't case sensitive here
+ if windows?
+ unless current_windows_tz.casecmp?(new_resource.timezone)
+ converge_by("setting timezone to '#{new_resource.timezone}'") do
+ shell_out!(["tzutil", "/s", new_resource.timezone])
+ end
+ end
+ else # linux / macos
+ converge_if_changed(:timezone) do
+ # Modern SUSE, Amazon, Fedora, RHEL, Ubuntu & Debian
+ if systemd?
+ # make sure we have the tzdata files
+ package suse? ? "timezone" : "tzdata"
+
+ shell_out!(["/usr/bin/timedatectl", "--no-ask-password", "set-timezone", new_resource.timezone])
+ else
+ case node["platform_family"]
+ # Old version of RHEL < 7 and Amazon 201X
+ when "rhel", "amazon"
+ # make sure we have the tzdata files
+ package "tzdata"
+
+ file "/etc/sysconfig/clock" do
+ owner "root"
+ group "root"
+ mode "0644"
+ action :create
+ content <<~CONTENT
+ ZONE="#{new_resource.timezone}"
+ UTC="true"
+ CONTENT
+ end
+
+ execute "tzdata-update" do
+ command "/usr/sbin/tzdata-update"
+ action :nothing
+ only_if { ::File.executable?("/usr/sbin/tzdata-update") }
+ subscribes :run, "file[/etc/sysconfig/clock]", :immediately
+ end
+
+ link "/etc/localtime" do
+ to "/usr/share/zoneinfo/#{new_resource.timezone}"
+ not_if { ::File.executable?("/usr/sbin/tzdata-update") }
+ end
+ when "mac_os_x"
+ shell_out!(["sudo", "systemsetup", "-settimezone", new_resource.timezone])
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/user.rb b/lib/chef/resource/user.rb
index 06dfe95bd4..408932175d 100644
--- a/lib/chef/resource/user.rb
+++ b/lib/chef/resource/user.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,159 +16,63 @@
# limitations under the License.
#
-require "chef/resource"
+require_relative "../resource"
class Chef
class Resource
class User < Chef::Resource
- resource_name :user_resource_abstract_base_class # this prevents magickal class name DSL wiring
- identity_attr :username
+ unified_mode true
- state_attrs :uid, :gid, :home
+ description "Use the **user** resource to add users, update existing users, remove users, and to lock/unlock user passwords."
default_action :create
allowed_actions :create, :remove, :modify, :manage, :lock, :unlock
- def initialize(name, run_context = nil)
- super
- @username = name
- @comment = nil
- @uid = nil
- @gid = nil
- @home = nil
- @shell = nil
- @password = nil
- @system = false
- @manage_home = false
- @force = false
- @non_unique = false
- @supports = {
- manage_home: false,
- non_unique: false,
- }
- @iterations = 27855
- @salt = nil
- end
-
- def username(arg = nil)
- set_or_return(
- :username,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def comment(arg = nil)
- set_or_return(
- :comment,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def uid(arg = nil)
- set_or_return(
- :uid,
- arg,
- :kind_of => [ String, Integer ]
- )
- end
-
- def gid(arg = nil)
- set_or_return(
- :gid,
- arg,
- :kind_of => [ String, Integer ]
- )
- end
+ property :username, String,
+ description: "An optional property to set the username value if it differs from the resource block's name.",
+ name_property: true
- alias_method :group, :gid
+ property :comment, String,
+ description: "The contents of the user comments field."
+
+ property :home, String,
+ description: "The location of the home directory."
+
+ property :salt, String,
+ description: "A SALTED-SHA512-PBKDF2 hash.",
+ desired_state: false
+
+ property :shell, String,
+ description: "The login shell."
+
+ property :password, String,
+ description: "The password shadow hash",
+ sensitive: true,
+ desired_state: false
+
+ property :non_unique, [ TrueClass, FalseClass ],
+ description: "Create a duplicate (non-unique) user account.",
+ default: false, desired_state: false
- def home(arg = nil)
- set_or_return(
- :home,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def shell(arg = nil)
- set_or_return(
- :shell,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def password(arg = nil)
- set_or_return(
- :password,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def salt(arg = nil)
- set_or_return(
- :salt,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def iterations(arg = nil)
- set_or_return(
- :iterations,
- arg,
- :kind_of => [ Integer ]
- )
- end
-
- def system(arg = nil)
- set_or_return(
- :system,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
-
- def manage_home(arg = nil)
- set_or_return(
- :manage_home,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
-
- def force(arg = nil)
- set_or_return(
- :force,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
-
- def non_unique(arg = nil)
- set_or_return(
- :non_unique,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
-
- def supports(args = {})
- if args.key?(:manage_home)
- Chef.log_deprecation "supports { manage_home: #{args[:manage_home]} } on the user resource is deprecated and will be removed in Chef 13, set manage_home: #{args[:manage_home]} instead"
- end
- if args.key?(:non_unique)
- Chef.log_deprecation "supports { non_unique: #{args[:non_unique]} } on the user resource is deprecated and will be removed in Chef 13, set non_unique: #{args[:non_unique]} instead"
- end
- super
- end
-
- def supports=(args)
- supports(args)
- end
+ property :manage_home, [ TrueClass, FalseClass ],
+ description: "Manage a user's home directory.\nWhen used with the :create action, a user's home directory is created based on HOME_DIR. If the home directory is missing, it is created unless CREATE_HOME in /etc/login.defs is set to no. When created, a skeleton set of files and subdirectories are included within the home directory.\nWhen used with the :modify action, a user's home directory is moved to HOME_DIR. If the home directory is missing, it is created unless CREATE_HOME in /etc/login.defs is set to no. The contents of the user's home directory are moved to the new location.",
+ default: false, desired_state: false
+
+ property :force, [ TrueClass, FalseClass ],
+ description: "Force the removal of a user. May be used only with the :remove action.",
+ default: false, desired_state: false
+
+ property :system, [ TrueClass, FalseClass ],
+ description: "Create a system user. This property may be used with useradd as the provider to create a system user which passes the -r flag to useradd.",
+ default: false
+
+ property :uid, [ String, Integer, NilClass ], # nil for backwards compat
+ description: "The numeric user identifier."
+
+ property :gid, [ String, Integer, NilClass ], # nil for backwards compat
+ description: "The numeric group identifier."
+
+ alias_method :group, :gid
end
end
end
diff --git a/lib/chef/resource/user/aix_user.rb b/lib/chef/resource/user/aix_user.rb
index 7c07db2e25..c0e4c01e20 100644
--- a/lib/chef/resource/user/aix_user.rb
+++ b/lib/chef/resource/user/aix_user.rb
@@ -1,5 +1,5 @@
#
-# Copyright:: Copyright 2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,13 +15,13 @@
# limitations under the License.
#
-require "chef/resource/user"
+require_relative "../user"
class Chef
class Resource
class User
class AixUser < Chef::Resource::User
- resource_name :aix_user
+ unified_mode true
provides :aix_user
provides :user, os: "aix"
diff --git a/lib/chef/resource/user/dscl_user.rb b/lib/chef/resource/user/dscl_user.rb
index 61517d8b44..91efd657de 100644
--- a/lib/chef/resource/user/dscl_user.rb
+++ b/lib/chef/resource/user/dscl_user.rb
@@ -1,5 +1,5 @@
#
-# Copyright:: Copyright 2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,16 +15,20 @@
# limitations under the License.
#
-require "chef/resource/user"
+require_relative "../user"
class Chef
class Resource
class User
class DsclUser < Chef::Resource::User
- resource_name :dscl_user
+ unified_mode true
provides :dscl_user
- provides :user, os: "darwin"
+ provides :user, platform: "mac_os_x", platform_version: "< 10.14"
+
+ property :iterations, Integer,
+ description: "macOS platform only. The number of iterations for a password with a SALTED-SHA512-PBKDF2 shadow hash.",
+ default: 27855, desired_state: false
end
end
end
diff --git a/lib/chef/resource/user/linux_user.rb b/lib/chef/resource/user/linux_user.rb
index ec60ac89bf..24406e5079 100644
--- a/lib/chef/resource/user/linux_user.rb
+++ b/lib/chef/resource/user/linux_user.rb
@@ -1,5 +1,5 @@
#
-# Copyright:: Copyright 2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,26 +15,17 @@
# limitations under the License.
#
-require "chef/resource/user"
+require_relative "../user"
class Chef
class Resource
class User
class LinuxUser < Chef::Resource::User
- resource_name :linux_user
+ unified_mode true
provides :linux_user
provides :user, os: "linux"
- def initialize(name, run_context = nil)
- super
- @supports = {
- manage_home: false,
- non_unique: false,
- }
- @manage_home = false
- end
-
end
end
end
diff --git a/lib/chef/resource/user/mac_user.rb b/lib/chef/resource/user/mac_user.rb
new file mode 100644
index 0000000000..2331283bbd
--- /dev/null
+++ b/lib/chef/resource/user/mac_user.rb
@@ -0,0 +1,122 @@
+#
+# Author:: Ryan Cragun (<ryan@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../user"
+
+class Chef
+ class Resource
+ class User
+ # Provide a user resource that is compatible with default TCC restrictions
+ # that were introduced in macOS 10.14.
+ #
+ # Changes:
+ #
+ # * This resource and the corresponding provider have been modified to
+ # work with default macOS TCC policies. Direct access to user binary
+ # plists are no longer permitted by default, thus we've chosen to use
+ # a combination of newer utilities for managing user lifecycles and older
+ # utilities for managing passwords.
+ #
+ # * Due to tooling changes that were necessitated by the new policy
+ # restrictions the mac_user resource is only suitable for use on macOS
+ # >= 10.14. Support for older platforms has been removed.
+ #
+ # New Features:
+ #
+ # * Primary group management is now included.
+ #
+ # * 'admin' is now a boolean property that configures a user to an admin.
+ #
+ # * 'admin_username' and 'admin_password' are new properties that define the
+ # admin user credentials required for toggling SecureToken for a user.
+ #
+ # The value of 'admin_username' must correspond to a system user that
+ # is part of the 'admin' with SecureToken enabled in order to toggle
+ # SecureToken.
+ #
+ # * 'secure_token' is a boolean property that sets the desired state
+ # for SecureToken. SecureToken token is required for FileVault full
+ # disk encryption.
+ #
+ # * 'secure_token_password' is the plaintext password required to enable
+ # or disable secure_token for a user. If no salt is specified we assume
+ # the 'password' property corresponds to a plaintext password and will
+ # attempt to use it in place of secure_token_password if it not set.
+ class MacUser < Chef::Resource::User
+ unified_mode true
+
+ provides :mac_user
+ provides :user, platform: "mac_os_x", platform_version: ">= 10.14"
+
+ introduced "15.3"
+
+ property :iterations, Integer,
+ description: "The number of iterations for a password with a SALTED-SHA512-PBKDF2 shadow hash.",
+ default: 57803, desired_state: false
+
+ # Overload gid to set our default gid to 20, the macOS "staff" group.
+ # We also allow a string group name here which we'll attempt to resolve
+ # or create in the provider.
+ property :gid, [Integer, String], description: "The numeric group identifier.", default: 20, coerce: ->(gid) do
+ begin
+ Integer(gid) # Try and coerce a group id string into an integer
+ rescue
+ gid # assume we have a group name
+ end
+ end
+
+ # Overload the password so we can set a length requirements and update the
+ # description.
+ property :password, String, description: "The plain text user password", sensitive: true, coerce: ->(password) {
+ # It would be nice if this could be in callbacks but we need the context
+ # of the resource to get the salt property so we have to do it in coerce.
+ if salt && password !~ /^[[:xdigit:]]{256}$/
+ raise Chef::Exceptions::User, "Password must be a SALTED-SHA512-PBKDF2 shadow hash entropy when a shadow hash salt is given"
+ end
+
+ password
+ },
+ callbacks: {
+ "Password length must be >= 4" => ->(password) { password.size >= 4 },
+ }
+
+ # Overload home so we set our default.
+ property :home, String, description: "The user home directory", default: lazy { "/Users/#{name}" }
+
+ property :admin, [TrueClass, FalseClass], description: "Create the user as an admin", default: false
+
+ # Hide a user account in the macOS login window
+ property :hidden, [TrueClass, FalseClass, nil], description: "Hide account from loginwindow and system preferences", default: nil, introduced: "15.8"
+
+ # TCC on macOS >= 10.14 requires admin credentials of an Admin user that
+ # has SecureToken enabled in order to toggle SecureToken.
+ property :admin_username, String, description: "Admin username for superuser actions"
+ property :admin_password, String, description: "Admin password for superuser actions", sensitive: true
+
+ property :secure_token, [TrueClass, FalseClass], description: "Enable SecureToken for the user", default: false
+ # In order to enable SecureToken for a user we require the plaintext password.
+ property :secure_token_password, String, description: "The plaintext password for enabling SecureToken", sensitive: true, default: lazy {
+ # In some cases the user can pass the plaintext value to "password" instead of
+ # SALTED-SHA512-PBKDF2 entropy. In those cases we'll default to the
+ # same value.
+ (salt.nil? && password) ? password : nil
+ }
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/user/pw_user.rb b/lib/chef/resource/user/pw_user.rb
index 873be19d59..2f97500970 100644
--- a/lib/chef/resource/user/pw_user.rb
+++ b/lib/chef/resource/user/pw_user.rb
@@ -1,5 +1,5 @@
#
-# Copyright:: Copyright 2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,13 +15,13 @@
# limitations under the License.
#
-require "chef/resource/user"
+require_relative "../user"
class Chef
class Resource
class User
class PwUser < Chef::Resource::User
- resource_name :pw_user
+ unified_mode true
provides :pw_user
provides :user, os: "freebsd"
diff --git a/lib/chef/resource/user/solaris_user.rb b/lib/chef/resource/user/solaris_user.rb
index bb897228b9..ab76cb7ae9 100644
--- a/lib/chef/resource/user/solaris_user.rb
+++ b/lib/chef/resource/user/solaris_user.rb
@@ -1,5 +1,5 @@
#
-# Copyright:: Copyright 2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,13 +15,13 @@
# limitations under the License.
#
-require "chef/resource/user"
+require_relative "../user"
class Chef
class Resource
class User
class SolarisUser < Chef::Resource::User
- resource_name :solaris_user
+ unified_mode true
provides :solaris_user
provides :user, os: %w{omnios solaris2}
diff --git a/lib/chef/resource/user/windows_user.rb b/lib/chef/resource/user/windows_user.rb
index d1a249fb50..d738ba1636 100644
--- a/lib/chef/resource/user/windows_user.rb
+++ b/lib/chef/resource/user/windows_user.rb
@@ -1,5 +1,5 @@
#
-# Copyright:: Copyright 2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,16 +15,25 @@
# limitations under the License.
#
-require "chef/resource/user"
+require_relative "../user"
class Chef
class Resource
class User
class WindowsUser < Chef::Resource::User
- resource_name :windows_user
+ unified_mode true
provides :windows_user
provides :user, os: "windows"
+
+ property :full_name, String,
+ description: "The full name of the user.",
+ introduced: "14.6"
+
+ # Override the property from the parent class to coerce to integer.
+ property :uid, [ String, Integer, NilClass ], # nil for backwards compat
+ description: "The numeric user identifier.",
+ coerce: proc { |n| n && Integer(n) rescue n }
end
end
end
diff --git a/lib/chef/resource/user_ulimit.rb b/lib/chef/resource/user_ulimit.rb
new file mode 100644
index 0000000000..d138eeabf3
--- /dev/null
+++ b/lib/chef/resource/user_ulimit.rb
@@ -0,0 +1,116 @@
+#
+# Copyright:: Copyright (c) Chef Software Inc.
+# Copyright:: 2012, Brightcove, Inc
+#
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../resource"
+
+class Chef
+ class Resource
+ class UserUlimit < Chef::Resource
+ unified_mode true
+
+ provides :user_ulimit
+
+ description "Use the **user_ulimit** resource to create individual ulimit files that are installed into the `/etc/security/limits.d/` directory."
+ introduced "16.0"
+ examples <<~DOC
+ **Set filehandle limit for the tomcat user**:
+
+ ```ruby
+ user_ulimit 'tomcat' do
+ filehandle_limit 8192
+ end
+ ```
+
+ **Specify a username that differs from the name given to the resource block**:
+
+ ```ruby
+ user_ulimit 'Bump filehandle limits for tomcat user' do
+ username 'tomcat'
+ filehandle_limit 8192
+ end
+ ```
+
+ **Set filehandle limit for the tomcat user with a non-default filename**:
+
+ ```ruby
+ user_ulimit 'tomcat' do
+ filehandle_limit 8192
+ filename 'tomcat_filehandle_limits.conf'
+ end
+ ```
+ DOC
+
+ property :username, String, name_property: true
+ property :filehandle_limit, [String, Integer]
+ property :filehandle_soft_limit, [String, Integer]
+ property :filehandle_hard_limit, [String, Integer]
+ property :process_limit, [String, Integer]
+ property :process_soft_limit, [String, Integer]
+ property :process_hard_limit, [String, Integer]
+ property :memory_limit, [String, Integer]
+ property :core_limit, [String, Integer]
+ property :core_soft_limit, [String, Integer]
+ property :core_hard_limit, [String, Integer]
+ property :stack_limit, [String, Integer]
+ property :stack_soft_limit, [String, Integer]
+ property :stack_hard_limit, [String, Integer]
+ property :rtprio_limit, [String, Integer]
+ property :rtprio_soft_limit, [String, Integer]
+ property :rtprio_hard_limit, [String, Integer]
+ property :virt_limit, [String, Integer]
+ property :filename, String,
+ coerce: proc { |m| m.end_with?(".conf") ? m : m + ".conf" },
+ default: lazy { |r| r.username == "*" ? "00_all_limits.conf" : "#{r.username}_limits.conf" }
+
+ action :create do
+ template "/etc/security/limits.d/#{new_resource.filename}" do
+ source ::File.expand_path("support/ulimit.erb", __dir__)
+ local true
+ mode "0644"
+ variables(
+ ulimit_user: new_resource.username,
+ filehandle_limit: new_resource.filehandle_limit,
+ filehandle_soft_limit: new_resource.filehandle_soft_limit,
+ filehandle_hard_limit: new_resource.filehandle_hard_limit,
+ process_limit: new_resource.process_limit,
+ process_soft_limit: new_resource.process_soft_limit,
+ process_hard_limit: new_resource.process_hard_limit,
+ memory_limit: new_resource.memory_limit,
+ core_limit: new_resource.core_limit,
+ core_soft_limit: new_resource.core_soft_limit,
+ core_hard_limit: new_resource.core_hard_limit,
+ stack_limit: new_resource.stack_limit,
+ stack_soft_limit: new_resource.stack_soft_limit,
+ stack_hard_limit: new_resource.stack_hard_limit,
+ rtprio_limit: new_resource.rtprio_limit,
+ rtprio_soft_limit: new_resource.rtprio_soft_limit,
+ rtprio_hard_limit: new_resource.rtprio_hard_limit,
+ virt_limit: new_resource.virt_limit
+ )
+ end
+ end
+
+ action :delete do
+ file "/etc/security/limits.d/#{new_resource.filename}" do
+ action :delete
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/whyrun_safe_ruby_block.rb b/lib/chef/resource/whyrun_safe_ruby_block.rb
index db11ba34d2..6dde0539a7 100644
--- a/lib/chef/resource/whyrun_safe_ruby_block.rb
+++ b/lib/chef/resource/whyrun_safe_ruby_block.rb
@@ -19,6 +19,8 @@
class Chef
class Resource
class WhyrunSafeRubyBlock < Chef::Resource::RubyBlock
+ provides :whyrun_safe_ruby_block
+ unified_mode true
end
end
end
diff --git a/lib/chef/resource/windows_ad_join.rb b/lib/chef/resource/windows_ad_join.rb
new file mode 100644
index 0000000000..731ce9333e
--- /dev/null
+++ b/lib/chef/resource/windows_ad_join.rb
@@ -0,0 +1,242 @@
+#
+# Author:: John Snow (<jsnow@chef.io>)
+# Copyright:: 2016-2018, John Snow
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../resource"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Resource
+ class WindowsAdJoin < Chef::Resource
+ provides :windows_ad_join
+
+ unified_mode true
+
+ description "Use the **windows_ad_join** resource to join a Windows Active Directory domain."
+ introduced "14.0"
+ examples <<~DOC
+ **Join a domain**
+
+ ```ruby
+ windows_ad_join 'ad.example.org' do
+ domain_user 'nick'
+ domain_password 'p@ssw0rd1'
+ end
+ ```
+
+ **Join a domain, as `win-workstation`**
+
+ ```ruby
+ windows_ad_join 'ad.example.org' do
+ domain_user 'nick'
+ domain_password 'p@ssw0rd1'
+ new_hostname 'win-workstation'
+ end
+ ```
+
+ **Leave the current domain and re-join the `local` workgroup**
+
+ ```ruby
+ windows_ad_join 'Leave domain' do
+ action :leave
+ workgroup 'local'
+ end
+ ```
+ DOC
+
+ property :domain_name, String,
+ description: "An optional property to set the FQDN of the Active Directory domain to join if it differs from the resource block's name.",
+ validation_message: "The 'domain_name' property must be a FQDN.",
+ regex: /.\../, # anything.anything
+ name_property: true
+
+ property :domain_user, String,
+ description: "The domain user that will be used to join the domain.",
+ required: true
+
+ property :domain_password, String,
+ description: "The password for the domain user. Note that this resource is set to hide sensitive information by default. ",
+ required: true
+
+ property :ou_path, String,
+ description: "The path to the Organizational Unit where the host will be placed."
+
+ property :reboot, Symbol,
+ equal_to: %i{immediate delayed never request_reboot reboot_now},
+ validation_message: "The reboot property accepts :immediate (reboot as soon as the resource completes), :delayed (reboot once the #{ChefUtils::Dist::Infra::PRODUCT} run completes), and :never (Don't reboot)",
+ description: "Controls the system reboot behavior post domain joining. Reboot immediately, after the #{ChefUtils::Dist::Infra::PRODUCT} run completes, or never. Note that a reboot is necessary for changes to take effect.",
+ default: :immediate
+
+ property :reboot_delay, Integer,
+ description: "The amount of time (in minutes) to delay a reboot request.",
+ default: 0,
+ introduced: "16.5"
+
+ property :new_hostname, String,
+ description: "Specifies a new hostname for the computer in the new domain.",
+ introduced: "14.5"
+
+ property :workgroup_name, String,
+ description: "Specifies the name of a workgroup to which the computer is added to when it is removed from the domain. The default value is WORKGROUP. This property is only applicable to the :leave action.",
+ introduced: "15.4"
+
+ # define this again so we can default it to true. Otherwise failures print the password
+ property :sensitive, [TrueClass, FalseClass],
+ default: true, desired_state: false
+
+ action :join do
+ description "Join the Active Directory domain."
+
+ unless on_desired_domain?
+ cmd = "$pswd = ConvertTo-SecureString \'#{new_resource.domain_password}\' -AsPlainText -Force;"
+ cmd << "$credential = New-Object System.Management.Automation.PSCredential (\"#{sanitize_usename}\",$pswd);"
+ cmd << "Add-Computer -DomainName #{new_resource.domain_name} -Credential $credential"
+ cmd << " -OUPath \"#{new_resource.ou_path}\"" if new_resource.ou_path
+ cmd << " -NewName \"#{new_resource.new_hostname}\"" if new_resource.new_hostname
+ cmd << " -Force"
+
+ converge_by("join Active Directory domain #{new_resource.domain_name}") do
+ ps_run = powershell_exec(cmd)
+ if ps_run.error?
+ if sensitive?
+ raise "Failed to join the domain #{new_resource.domain_name}: *suppressed sensitive resource output*"
+ else
+ raise "Failed to join the domain #{new_resource.domain_name}: #{ps_run.errors}"
+ end
+ end
+
+ unless new_resource.reboot == :never
+ reboot "Reboot to join domain #{new_resource.domain_name}" do
+ action clarify_reboot(new_resource.reboot)
+ delay_mins new_resource.reboot_delay
+ reason "Reboot to join domain #{new_resource.domain_name}"
+ end
+ end
+ end
+ end
+ end
+
+ action :leave do
+ description "Leave the Active Directory domain."
+
+ if joined_to_domain?
+ cmd = ""
+ cmd << "$pswd = ConvertTo-SecureString \'#{new_resource.domain_password}\' -AsPlainText -Force;"
+ cmd << "$credential = New-Object System.Management.Automation.PSCredential (\"#{sanitize_usename}\",$pswd);"
+ cmd << "Remove-Computer"
+ cmd << " -UnjoinDomainCredential $credential"
+ cmd << " -NewName \"#{new_resource.new_hostname}\"" if new_resource.new_hostname
+ cmd << " -WorkgroupName \"#{new_resource.workgroup_name}\"" if new_resource.workgroup_name
+ cmd << " -Force"
+
+ converge_by("leave Active Directory domain #{node_domain}") do
+ ps_run = powershell_exec(cmd)
+ if ps_run.error?
+ if sensitive?
+ raise "Failed to leave the domain #{node_domain}: *suppressed sensitive resource output*"
+ else
+ raise "Failed to leave the domain #{node_domain}: #{ps_run.errors}"
+ end
+ end
+
+ unless new_resource.reboot == :never
+ reboot "Reboot to leave domain #{new_resource.domain_name}" do
+ action clarify_reboot(new_resource.reboot)
+ delay_mins new_resource.reboot_delay
+ reason "Reboot to leave domain #{new_resource.domain_name}"
+ end
+ end
+ end
+ end
+ end
+
+ action_class do
+ #
+ # @return [String] The domain name the node is joined to. When the node
+ # is not joined to a domain this will return the name of the
+ # workgroup the node is a member of.
+ #
+ def node_domain
+ node_domain = powershell_exec!("(Get-WmiObject Win32_ComputerSystem).Domain")
+ raise "Failed to check if the system is joined to the domain #{new_resource.domain_name}: #{node_domain.errors}}" if node_domain.error?
+
+ node_domain.result.downcase.strip
+ end
+
+ #
+ # @return [String] The workgroup the node is a member of. This will
+ # return an empty string if the system is not a member of a
+ # workgroup.
+ #
+ def node_workgroup
+ node_workgroup = powershell_exec!("(Get-WmiObject Win32_ComputerSystem).Workgroup")
+ raise "Failed to check if the system is currently a member of a workgroup" if node_workgroup.error?
+
+ node_workgroup.result
+ end
+
+ #
+ # @return [true, false] Whether or not the node is joined to ANY domain
+ #
+ def joined_to_domain?
+ node_workgroup.empty? && !node_domain.empty?
+ end
+
+ #
+ # @return [true, false] Whether or not the node is joined to the domain
+ # defined by the resource :domain_name property.
+ #
+ def on_desired_domain?
+ node_domain == new_resource.domain_name.downcase
+ end
+
+ #
+ # @return [String] the correct user and domain to use.
+ # if the domain_user property contains an @ symbol followed by any number of non white space characters
+ # then we assume it is a user from another domain than the one specified in the resource domain_name property.
+ # if this is the case we do not append the domain_name property to the domain_user property
+ # the domain_user and domain_name form the UPN (userPrincipalName)
+ # The specification for the UPN format is RFC 822
+ # links: https://docs.microsoft.com/en-us/windows/win32/ad/naming-properties#userprincipalname https://tools.ietf.org/html/rfc822
+ # regex: https://rubular.com/r/isAWojpTMKzlnp
+ def sanitize_usename
+ if /@/.match?(new_resource.domain_user)
+ new_resource.domain_user
+ else
+ "#{new_resource.domain_user}@#{new_resource.domain_name}"
+ end
+ end
+
+ # This resource historically took `:immediate` and `:delayed` as arguments to the reboot property but then
+ # tried to shove that straight to the `reboot` resource which objected strenuously
+ def clarify_reboot(reboot_action)
+ case reboot_action
+ when :immediate
+ :reboot_now
+ when :delayed
+ :request_reboot
+ else
+ reboot_action
+ end
+ end
+
+ def sensitive?
+ !!new_resource.sensitive
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_audit_policy.rb b/lib/chef/resource/windows_audit_policy.rb
new file mode 100644
index 0000000000..433e18e197
--- /dev/null
+++ b/lib/chef/resource/windows_audit_policy.rb
@@ -0,0 +1,232 @@
+#
+# Author:: Ross Moles (<rmoles@chef.io>)
+# Author:: Rachel Rice (<rrice@chef.io>)
+# Author:: Davin Taddeo (<davin@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class WindowsAuditPolicy < Chef::Resource
+ WIN_AUDIT_SUBCATEGORIES = ["Account Lockout",
+ "Application Generated",
+ "Application Group Management",
+ "Audit Policy Change",
+ "Authentication Policy Change",
+ "Authorization Policy Change",
+ "Central Policy Staging",
+ "Certification Services",
+ "Computer Account Management",
+ "Credential Validation",
+ "DPAPI Activity",
+ "Detailed Directory Service Replication",
+ "Detailed File Share",
+ "Directory Service Access",
+ "Directory Service Changes",
+ "Directory Service Replication",
+ "Distribution Group Management",
+ "File Share",
+ "File System",
+ "Filtering Platform Connection",
+ "Filtering Platform Packet Drop",
+ "Filtering Platform Policy Change",
+ "Group Membership",
+ "Handle Manipulation",
+ "IPsec Driver",
+ "IPsec Extended Mode",
+ "IPsec Main Mode",
+ "IPsec Quick Mode",
+ "Kerberos Authentication Service",
+ "Kerberos Service Ticket Operations",
+ "Kernel Object",
+ "Logoff",
+ "Logon",
+ "MPSSVC Rule-Level Policy Change",
+ "Network Policy Server",
+ "Non Sensitive Privilege Use",
+ "Other Account Logon Events",
+ "Other Account Management Events",
+ "Other Logon/Logoff Events",
+ "Other Object Access Events",
+ "Other Policy Change Events",
+ "Other Privilege Use Events",
+ "Other System Events",
+ "Plug and Play Events",
+ "Process Creation",
+ "Process Termination",
+ "RPC Events",
+ "Registry",
+ "Removable Storage",
+ "SAM",
+ "Security Group Management",
+ "Security State Change",
+ "Security System Extension",
+ "Sensitive Privilege Use",
+ "Special Logon",
+ "System Integrity",
+ "Token Right Adjusted Events",
+ "User / Device Claims",
+ "User Account Management",
+ ].freeze
+
+ unified_mode true
+
+ provides :windows_audit_policy
+
+ description "Use the **windows_audit_policy** resource to configure system level and per-user Windows advanced audit policy settings."
+ introduced "16.2"
+
+ examples <<~DOC
+ **Set Logon and Logoff policy to "Success and Failure"**:
+
+ ```ruby
+ windows_audit_policy "Set Audit Policy for 'Logon and Logoff' actions to 'Success and Failure'" do
+ subcategory %w(Logon Logoff)
+ success true
+ failure true
+ action :set
+ end
+ ```
+
+ **Set Credential Validation policy to "Success"**:
+
+ ```ruby
+ windows_audit_policy "Set Audit Policy for 'Credential Validation' actions to 'Success'" do
+ subcategory 'Credential Validation'
+ success true
+ failure false
+ action :set
+ end
+ ```
+
+ **Enable CrashOnAuditFail option**:
+
+ ```ruby
+ windows_audit_policy 'Enable CrashOnAuditFail option' do
+ crash_on_audit_fail true
+ action :set
+ end
+ ```
+ DOC
+
+ property :subcategory, [String, Array],
+ coerce: proc { |p| Array(p) },
+ description: "The audit policy subcategory, specified by GUID or name. Applied system-wide if no user is specified.",
+ callbacks: { "Subcategories entered should be actual advanced audit policy subcategories" => proc { |n| (Array(n) - WIN_AUDIT_SUBCATEGORIES).empty? } }
+
+ property :success, [true, false],
+ description: "Specify success auditing. By setting this property to true the resource will enable success for the category or sub category. Success is the default and is applied if neither success nor failure are specified."
+
+ property :failure, [true, false],
+ description: "Specify failure auditing. By setting this property to true the resource will enable failure for the category or sub category. Success is the default and is applied if neither success nor failure are specified."
+
+ property :include_user, String,
+ description: "The audit policy specified by the category or subcategory is applied per-user if specified. When a user is specified, include user. Include and exclude cannot be used at the same time."
+
+ property :exclude_user, String,
+ description: "The audit policy specified by the category or subcategory is applied per-user if specified. When a user is specified, exclude user. Include and exclude cannot be used at the same time."
+
+ property :crash_on_audit_fail, [true, false],
+ description: "Setting this audit policy option to true will cause the system to crash if the auditing system is unable to log events."
+
+ property :full_privilege_auditing, [true, false],
+ description: "Setting this audit policy option to true will force the audit of all privilege changes except SeAuditPrivilege. Setting this property may cause the logs to fill up more quickly."
+
+ property :audit_base_objects, [true, false],
+ description: "Setting this audit policy option to true will force the system to assign a System Access Control List to named objects to enable auditing of base objects such as mutexes."
+
+ property :audit_base_directories, [true, false],
+ description: "Setting this audit policy option to true will force the system to assign a System Access Control List to named objects to enable auditing of container objects such as directories."
+
+ action :set do
+ unless new_resource.subcategory.nil?
+ new_resource.subcategory.each do |subcategory|
+ next if subcategory_configured?(subcategory, new_resource.success, new_resource.failure)
+
+ s_val = new_resource.success ? "enable" : "disable"
+ f_val = new_resource.failure ? "enable" : "disable"
+ converge_by "Update Audit Policy for \"#{subcategory}\" to Success:#{s_val} and Failure:#{f_val}" do
+ cmd = "auditpol /set "
+ cmd += "/user:\"#{new_resource.include_user}\" /include " if new_resource.include_user
+ cmd += "/user:\"#{new_resource.exclude_user}\" /exclude " if new_resource.exclude_user
+ cmd += "/subcategory:\"#{subcategory}\" /success:#{s_val} /failure:#{f_val}"
+ powershell_exec!(cmd)
+ end
+ end
+ end
+
+ if !new_resource.crash_on_audit_fail.nil? && option_configured?("CrashOnAuditFail", new_resource.crash_on_audit_fail)
+ val = new_resource.crash_on_audit_fail ? "Enable" : "Disable"
+ converge_by "Configure Audit: CrashOnAuditFail to #{val}" do
+ cmd = "auditpol /set /option:CrashOnAuditFail /value:#{val}"
+ powershell_exec!(cmd)
+ end
+ end
+
+ if !new_resource.full_privilege_auditing.nil? && option_configured?("FullPrivilegeAuditing", new_resource.full_privilege_auditing)
+ val = new_resource.full_privilege_auditing ? "Enable" : "Disable"
+ converge_by "Configure Audit: FullPrivilegeAuditing to #{val}" do
+ cmd = "auditpol /set /option:FullPrivilegeAuditing /value:#{val}"
+ powershell_exec!(cmd)
+ end
+ end
+
+ if !new_resource.audit_base_directories.nil? && option_configured?("AuditBaseDirectories", new_resource.audit_base_directories)
+ val = new_resource.audit_base_directories ? "Enable" : "Disable"
+ converge_by "Configure Audit: AuditBaseDirectories to #{val}" do
+ cmd = "auditpol /set /option:AuditBaseDirectories /value:#{val}"
+ powershell_exec!(cmd)
+ end
+ end
+
+ if !new_resource.audit_base_objects.nil? && option_configured?("AuditBaseObjects", new_resource.audit_base_objects)
+ val = new_resource.audit_base_objects ? "Enable" : "Disable"
+ converge_by "Configure Audit: AuditBaseObjects to #{val}" do
+ cmd = "auditpol /set /option:AuditBaseObjects /value:#{val}"
+ powershell_exec!(cmd)
+ end
+ end
+ end
+
+ action_class do
+ def subcategory_configured?(sub_cat, success_value, failure_value)
+ setting = if success_value && failure_value
+ "Success and Failure$"
+ elsif success_value && !failure_value
+ "Success$"
+ elsif !success_value && failure_value
+ "#{sub_cat}\\s+Failure$"
+ else
+ "No Auditing"
+ end
+ powershell_exec!(<<-CODE).result
+ $auditpol_config = auditpol /get /subcategory:"#{sub_cat}"
+ if ($auditpol_config | Select-String "#{setting}") { return $true } else { return $false }
+ CODE
+ end
+
+ def option_configured?(option_name, option_setting)
+ setting = option_setting ? "Enabled$" : "Disabled$"
+ powershell_exec!(<<-CODE).result
+ $auditpol_config = auditpol /get /option:#{option_name}
+ if ($auditpol_config | Select-String "#{setting}") { return $true } else { return $false }
+ CODE
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_auto_run.rb b/lib/chef/resource/windows_auto_run.rb
new file mode 100644
index 0000000000..4885a02676
--- /dev/null
+++ b/lib/chef/resource/windows_auto_run.rb
@@ -0,0 +1,99 @@
+#
+# Author:: Paul Morton (<pmorton@biaprotect.com>)
+# Copyright:: 2011-2018, Business Intelligence Associates, Inc.
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class WindowsAutorun < Chef::Resource
+ unified_mode true
+
+ provides(:windows_auto_run) { true }
+
+ description "Use the **windows_auto_run** resource to set applications to run at login."
+ introduced "14.0"
+ examples <<~DOC
+ **Run BGInfo at login**
+
+ ```ruby
+ windows_auto_run 'BGINFO' do
+ program 'C:/Sysinternals/bginfo.exe'
+ args '\'C:/Sysinternals/Config.bgi\' /NOLICPROMPT /TIMER:0'
+ action :create
+ end
+ ```
+ DOC
+
+ property :program_name, String,
+ description: "The name of the program to run at login if it differs from the resource block's name.",
+ name_property: true
+
+ property :path, String,
+ coerce: proc { |x| x.tr("/", "\\") }, # make sure we have windows paths for the registry
+ description: "The path to the program that will run at login."
+
+ property :args, String,
+ description: "Any arguments to be used with the program."
+
+ property :root, Symbol,
+ description: "The registry root key to put the entry under.",
+ equal_to: %i{machine user},
+ default: :machine
+
+ alias_method :program, :path
+
+ action :create do
+ description "Create an item to be run at login."
+
+ data = "\"#{new_resource.path}\""
+ data << " #{new_resource.args}" if new_resource.args
+
+ registry_key registry_path do
+ values [{
+ name: new_resource.program_name,
+ type: :string,
+ data: data,
+ }]
+ action :create
+ end
+ end
+
+ action :remove do
+ description "Remove an item that was previously setup to run at login"
+
+ registry_key registry_path do
+ values [{
+ name: new_resource.program_name,
+ type: :string,
+ data: "",
+ }]
+ action :delete
+ end
+ end
+
+ action_class do
+ # determine the full registry path based on the root property
+ # @return [String]
+ def registry_path
+ { machine: "HKLM", user: "HKCU" }[new_resource.root] + \
+ '\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_certificate.rb b/lib/chef/resource/windows_certificate.rb
new file mode 100644
index 0000000000..5800fe0f45
--- /dev/null
+++ b/lib/chef/resource/windows_certificate.rb
@@ -0,0 +1,356 @@
+#
+# Author:: Richard Lavey (richard.lavey@calastone.com)
+#
+# Copyright:: 2015-2017, Calastone Ltd.
+# Copyright:: Copyright (c) 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_relative "../util/path_helper"
+require_relative "../resource"
+module Win32
+ autoload :Certstore, "win32-certstore" if Chef::Platform.windows?
+end
+autoload :OpenSSL, "openssl"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Resource
+ class WindowsCertificate < Chef::Resource
+ unified_mode true
+
+ provides :windows_certificate
+
+ description "Use the **windows_certificate** resource to install a certificate into the Windows certificate store from a file. The resource grants read-only access to the private key for designated accounts. Due to current limitations in WinRM, installing certificates remotely may not work if the operation requires a user profile. Operations on the local machine store should still work."
+ introduced "14.7"
+ examples <<~DOC
+ **Add PFX cert to local machine personal store and grant accounts read-only access to private key**
+
+ ```ruby
+ windows_certificate 'c:/test/mycert.pfx' do
+ pfx_password 'password'
+ private_key_acl ["acme\\fred", "pc\\jane"]
+ end
+ ```
+
+ **Add cert to trusted intermediate store**
+
+ ```ruby
+ windows_certificate 'c:/test/mycert.cer' do
+ store_name 'CA'
+ end
+ ```
+
+ **Remove all certificates matching the subject**
+
+ ```ruby
+ windows_certificate 'me.acme.com' do
+ action :delete
+ end
+ ```
+ DOC
+
+ property :source, String,
+ description: "The source file (for create and acl_add), thumbprint (for delete and acl_add) or subject (for delete) if it differs from the resource block's name.",
+ name_property: true
+
+ property :pfx_password, String,
+ description: "The password to access the source if it is a pfx file."
+
+ property :private_key_acl, Array,
+ description: "An array of 'domain\\account' entries to be granted read-only access to the certificate's private key. Not idempotent."
+
+ property :store_name, String,
+ description: "The certificate store to manipulate.",
+ default: "MY", equal_to: ["TRUSTEDPUBLISHER", "TrustedPublisher", "CLIENTAUTHISSUER", "REMOTE DESKTOP", "ROOT", "TRUSTEDDEVICES", "WEBHOSTING", "CA", "AUTHROOT", "TRUSTEDPEOPLE", "MY", "SMARTCARDROOT", "TRUST", "DISALLOWED"]
+
+ property :user_store, [TrueClass, FalseClass],
+ description: "Use the user store of the local machine store if set to false.",
+ default: false
+
+ property :cert_path, String,
+ description: "The path to the certificate."
+
+ # lazy used to set default value of sensitive to true if password is set
+ property :sensitive, [TrueClass, FalseClass],
+ description: "Ensure that sensitive resource data is not logged by the #{ChefUtils::Dist::Infra::CLIENT}.",
+ default: lazy { pfx_password ? true : false }, skip_docs: true
+
+ property :exportable, [TrueClass, FalseClass],
+ description: "Ensure that imported pfx certificate is exportable. Please provide 'true' if you want the certificate to be exportable.",
+ default: false,
+ introduced: "16.8"
+
+ action :create do
+ description "Creates or updates a certificate."
+
+ # Extension of the certificate
+ ext = ::File.extname(new_resource.source)
+
+ # PFX certificates contains private keys and we import them with some other approach
+ import_certificates(fetch_cert_object(ext), (ext == ".pfx"))
+ end
+
+ # acl_add is a modify-if-exists operation : not idempotent
+ action :acl_add do
+ description "Adds read-only entries to a certificate's private key ACL."
+
+ if ::File.exist?(new_resource.source)
+ hash = "$cert.GetCertHashString()"
+ code_script = cert_script(false)
+ guard_script = cert_script(false)
+ else
+ # make sure we have no spaces in the hash string
+ hash = "\"#{new_resource.source.gsub(/\s/, "")}\""
+ code_script = ""
+ guard_script = ""
+ end
+ code_script << acl_script(hash)
+ guard_script << cert_exists_script(hash)
+
+ powershell_script "setting the acls on #{new_resource.source} in #{cert_location}\\#{new_resource.store_name}" do
+ convert_boolean_return true
+ code code_script
+ only_if guard_script
+ sensitive if new_resource.sensitive
+ end
+ end
+
+ action :delete do
+ description "Deletes a certificate."
+ cert_obj = fetch_cert
+ if cert_obj
+ converge_by("Deleting certificate #{new_resource.source} from Store #{new_resource.store_name}") do
+ delete_cert
+ end
+ else
+ Chef::Log.debug("Certificate not found")
+ end
+ end
+
+ action :fetch do
+ description "Fetches a certificate."
+
+ cert_obj = fetch_cert
+ if cert_obj
+ show_or_store_cert(cert_obj)
+ else
+ Chef::Log.debug("Certificate not found")
+ end
+ end
+
+ action :verify do
+ description ""
+
+ out = verify_cert
+ if !!out == out
+ out = out ? "Certificate is valid" : "Certificate not valid"
+ end
+ Chef::Log.info(out.to_s)
+ end
+
+ action_class do
+ def add_cert(cert_obj)
+ store = ::Win32::Certstore.open(new_resource.store_name)
+ store.add(cert_obj)
+ end
+
+ def add_pfx_cert
+ exportable = new_resource.exportable ? 1 : 0
+ store = ::Win32::Certstore.open(new_resource.store_name)
+ store.add_pfx(new_resource.source, new_resource.pfx_password, exportable)
+ end
+
+ def delete_cert
+ store = ::Win32::Certstore.open(new_resource.store_name)
+ store.delete(new_resource.source)
+ end
+
+ def fetch_cert
+ store = ::Win32::Certstore.open(new_resource.store_name)
+ store.get(new_resource.source)
+ end
+
+ # Checks whether a certificate with the given thumbprint
+ # is already present and valid in certificate store
+ # If the certificate is not present, verify_cert returns a String: "Certificate not found"
+ # But if it is present but expired, it returns a Boolean: false
+ # Otherwise, it returns a Boolean: true
+ def verify_cert(thumbprint = new_resource.source)
+ store = ::Win32::Certstore.open(new_resource.store_name)
+ store.valid?(thumbprint)
+ end
+
+ def show_or_store_cert(cert_obj)
+ if new_resource.cert_path
+ export_cert(cert_obj, new_resource.cert_path)
+ if ::File.size(new_resource.cert_path) > 0
+ Chef::Log.info("Certificate export in #{new_resource.cert_path}")
+ else
+ ::File.delete(new_resource.cert_path)
+ end
+ else
+ Chef::Log.info(cert_obj.display)
+ end
+ end
+
+ def export_cert(cert_obj, cert_path)
+ out_file = ::File.new(cert_path, "w+")
+ case ::File.extname(cert_path)
+ when ".pem"
+ out_file.puts(cert_obj.to_pem)
+ when ".der"
+ out_file.puts(cert_obj.to_der)
+ when ".cer"
+ cert_out = shell_out("openssl x509 -text -inform DER -in #{cert_obj.to_pem} -outform CER").stdout
+ out_file.puts(cert_out)
+ when ".crt"
+ cert_out = shell_out("openssl x509 -text -inform DER -in #{cert_obj.to_pem} -outform CRT").stdout
+ out_file.puts(cert_out)
+ when ".pfx"
+ cert_out = shell_out("openssl pkcs12 -export -nokeys -in #{cert_obj.to_pem} -outform PFX").stdout
+ out_file.puts(cert_out)
+ when ".p7b"
+ cert_out = shell_out("openssl pkcs7 -export -nokeys -in #{cert_obj.to_pem} -outform P7B").stdout
+ out_file.puts(cert_out)
+ else
+ Chef::Log.info("Supported certificate format .pem, .der, .cer, .crt, .pfx and .p7b")
+ end
+ out_file.close
+ end
+
+ def cert_location
+ @location ||= new_resource.user_store ? "CurrentUser" : "LocalMachine"
+ end
+
+ def cert_script(persist)
+ cert_script = "$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2"
+ file = Chef::Util::PathHelper.cleanpath(new_resource.source)
+ cert_script << " \"#{file}\""
+ if ::File.extname(file.downcase) == ".pfx"
+ cert_script << ", \"#{new_resource.pfx_password}\""
+ if persist && new_resource.user_store
+ cert_script << ", ([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet)"
+ elsif persist
+ cert_script << ", ([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeyset)"
+ end
+ end
+ cert_script << "\n"
+ end
+
+ def cert_exists_script(hash)
+ <<-EOH
+ $hash = #{hash}
+ Test-Path "Cert:\\#{cert_location}\\#{new_resource.store_name}\\$hash"
+ EOH
+ end
+
+ def within_store_script
+ inner_script = yield "$store"
+ <<-EOH
+ $store = New-Object System.Security.Cryptography.X509Certificates.X509Store "#{new_resource.store_name}", ([System.Security.Cryptography.X509Certificates.StoreLocation]::#{cert_location})
+ $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
+ #{inner_script}
+ $store.Close()
+ EOH
+ end
+
+ def acl_script(hash)
+ return "" if new_resource.private_key_acl.nil? || new_resource.private_key_acl.empty?
+
+ # this PS came from http://blogs.technet.com/b/operationsguy/archive/2010/11/29/provide-access-to-private-keys-commandline-vs-powershell.aspx
+ # and from https://msdn.microsoft.com/en-us/library/windows/desktop/bb204778(v=vs.85).aspx
+ set_acl_script = <<-EOH
+ $hash = #{hash}
+ $storeCert = Get-ChildItem "cert:\\#{cert_location}\\#{new_resource.store_name}\\$hash"
+ if ($storeCert -eq $null) { throw 'no key exists.' }
+ $keyname = $storeCert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
+ if ($keyname -eq $null) { throw 'no private key exists.' }
+ if ($storeCert.PrivateKey.CspKeyContainerInfo.MachineKeyStore)
+ {
+ $fullpath = "$Env:ProgramData\\Microsoft\\Crypto\\RSA\\MachineKeys\\$keyname"
+ }
+ else
+ {
+ $currentUser = New-Object System.Security.Principal.NTAccount($Env:UserDomain, $Env:UserName)
+ $userSID = $currentUser.Translate([System.Security.Principal.SecurityIdentifier]).Value
+ $fullpath = "$Env:ProgramData\\Microsoft\\Crypto\\RSA\\$userSID\\$keyname"
+ }
+ EOH
+ new_resource.private_key_acl.each do |name|
+ set_acl_script << "$uname='#{name}'; icacls $fullpath /grant $uname`:RX\n"
+ end
+ set_acl_script
+ end
+
+ # Method returns an OpenSSL::X509::Certificate object. Might also return multiple certificates if present in certificate path
+ #
+ # Based on its extension, the certificate contents are used to initialize
+ # PKCS12 (PFX), PKCS7 (P7B) objects which contains OpenSSL::X509::Certificate.
+ #
+ # @note Other then PEM, all the certificates are usually in binary format, and hence
+ # their contents are loaded by using File.binread
+ #
+ # @param ext [String] Extension of the certificate
+ #
+ # @return [OpenSSL::X509::Certificate] Object containing certificate's attributes
+ #
+ # @raise [OpenSSL::PKCS12::PKCS12Error] When incorrect password is provided for PFX certificate
+ #
+ def fetch_cert_object(ext)
+ contents = ::File.binread(new_resource.source)
+
+ case ext
+ when ".pfx"
+ pfx = OpenSSL::PKCS12.new(contents, new_resource.pfx_password)
+ if pfx.ca_certs.nil?
+ pfx.certificate
+ else
+ [pfx.certificate] + pfx.ca_certs
+ end
+ when ".p7b"
+ OpenSSL::PKCS7.new(contents).certificates
+ else
+ OpenSSL::X509::Certificate.new(contents)
+ end
+ end
+
+ # Imports the certificate object into cert store
+ #
+ # @param cert_objs [OpenSSL::X509::Certificate] Object containing certificate's attributes
+ #
+ # @param is_pfx [Boolean] true if we want to import a PFX certificate
+ #
+ def import_certificates(cert_objs, is_pfx)
+ [cert_objs].flatten.each do |cert_obj|
+ thumbprint = OpenSSL::Digest.new("SHA1", cert_obj.to_der).to_s # Fetch its thumbprint
+ # Need to check if return value is Boolean:true
+ # If not then the given certificate should be added in certstore
+ if verify_cert(thumbprint) == true
+ Chef::Log.debug("Certificate is already present")
+ else
+ converge_by("Adding certificate #{new_resource.source} into Store #{new_resource.store_name}") do
+ if is_pfx
+ add_pfx_cert
+ else
+ add_cert(cert_obj)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_dfs_folder.rb b/lib/chef/resource/windows_dfs_folder.rb
new file mode 100644
index 0000000000..31f6814bcf
--- /dev/null
+++ b/lib/chef/resource/windows_dfs_folder.rb
@@ -0,0 +1,77 @@
+#
+# Author:: Jason Field
+#
+# Copyright:: 2018, Calastone Ltd.
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class WindowsDfsFolder < Chef::Resource
+ unified_mode true
+
+ provides :windows_dfs_folder
+
+ description "Use the **windows_dfs_folder** resource to creates a folder within DFS as many levels deep as required."
+ introduced "15.0"
+
+ property :folder_path, String,
+ description: "An optional property to set the path of the dfs folder if it differs from the resource block's name.",
+ name_property: true
+
+ property :namespace_name, String,
+ description: "The namespace this should be created within.",
+ required: true
+
+ property :target_path, String,
+ description: "The target that this path will connect you to."
+
+ property :description, String,
+ description: "Description for the share."
+
+ action :create do
+ description "Creates the folder in dfs namespace."
+
+ raise "target_path is required for install" unless property_is_set?(:target_path)
+ raise "description is required for install" unless property_is_set?(:description)
+
+ powershell_script "Create or Update DFS Folder" do
+ code <<-EOH
+
+ $needs_creating = (Get-DfsnFolder -Path '\\\\#{ENV["COMPUTERNAME"]}\\#{new_resource.namespace_name}\\#{new_resource.folder_path}' -ErrorAction SilentlyContinue) -eq $null
+ if (!($needs_creating))
+ {
+ Remove-DfsnFolder -Path '\\\\#{ENV["COMPUTERNAME"]}\\#{new_resource.namespace_name}\\#{new_resource.folder_path}' -Force
+ }
+ New-DfsnFolder -Path '\\\\#{ENV["COMPUTERNAME"]}\\#{new_resource.namespace_name}\\#{new_resource.folder_path}' -TargetPath '#{new_resource.target_path}' -Description '#{new_resource.description}'
+ EOH
+ not_if "return ((Get-DfsnFolder -Path '\\\\#{ENV["COMPUTERNAME"]}\\#{new_resource.namespace_name}\\#{new_resource.folder_path}' -ErrorAction SilentlyContinue).Description -eq '#{new_resource.description}' -and (Get-DfsnFolderTarget -Path '\\\\#{ENV["COMPUTERNAME"]}\\#{new_resource.namespace_name}\\#{new_resource.folder_path}').TargetPath -eq '#{new_resource.target_path}' )"
+ end
+ end
+
+ action :delete do
+ description "Deletes the folder in the dfs namespace."
+
+ powershell_script "Delete DFS Namespace" do
+ code <<-EOH
+ Remove-DfsnFolder -Path '\\\\#{ENV["COMPUTERNAME"]}\\#{new_resource.namespace_name}\\#{new_resource.folder_path}' -Force
+ EOH
+ only_if "return ((Get-DfsnFolder -Path '\\\\#{ENV["COMPUTERNAME"]}\\#{new_resource.namespace_name}\\#{new_resource.folder_path}' ) -ne $null)"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_dfs_namespace.rb b/lib/chef/resource/windows_dfs_namespace.rb
new file mode 100644
index 0000000000..ddd8a0ee26
--- /dev/null
+++ b/lib/chef/resource/windows_dfs_namespace.rb
@@ -0,0 +1,115 @@
+#
+# Author:: Jason Field
+#
+# Copyright:: 2018, Calastone Ltd.
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class WindowsDfsNamespace < Chef::Resource
+ unified_mode true
+
+ provides :windows_dfs_namespace
+
+ description "Use the **windows_dfs_namespace** resource to creates a share and DFS namespace on a Windows server."
+ introduced "15.0"
+
+ property :namespace_name, String,
+ description: "An optional property to set the dfs namespace if it differs from the resource block's name.",
+ name_property: true
+
+ property :description, String,
+ description: "Description of the share.",
+ required: true
+
+ property :full_users, Array,
+ description: "Determines which users should have full access to the share.",
+ default: ['BUILTIN\\administrators']
+
+ property :change_users, Array,
+ description: "Determines which users should have change access to the share.",
+ default: []
+
+ property :read_users, Array,
+ description: "Determines which users should have read access to the share.",
+ default: []
+
+ property :root, String,
+ description: "The root from which to create the DFS tree. Defaults to C:\\DFSRoots.",
+ default: 'C:\\DFSRoots'
+
+ action :create do
+ description "Creates the dfs namespace on the server."
+
+ directory file_path do
+ action :create
+ recursive true
+ end
+
+ windows_share new_resource.namespace_name do
+ action :create
+ path file_path
+ full_users new_resource.full_users
+ change_users new_resource.change_users
+ read_users new_resource.read_users
+ end
+
+ powershell_script "Create DFS Namespace" do
+ code <<-EOH
+ $needs_creating = (Get-DfsnRoot -Path '\\\\#{ENV["COMPUTERNAME"]}\\#{new_resource.namespace_name}' -ErrorAction SilentlyContinue) -eq $null
+ if ($needs_creating)
+ {
+ New-DfsnRoot -Path '\\\\#{ENV["COMPUTERNAME"]}\\#{new_resource.namespace_name}' -TargetPath '\\\\#{ENV["COMPUTERNAME"]}\\#{new_resource.namespace_name}' -Type Standalone -Description '#{new_resource.description}'
+ }
+ else
+ {
+ Set-DfsnRoot -Path '\\\\#{ENV["COMPUTERNAME"]}\\#{new_resource.namespace_name}' -Description '#{new_resource.description}'
+ }
+ EOH
+ not_if "return (Get-DfsnRoot -Path '\\\\#{ENV["COMPUTERNAME"]}\\#{new_resource.namespace_name}' -ErrorAction SilentlyContinue).description -eq '#{new_resource.description}'"
+ end
+ end
+
+ action :delete do
+ description "Deletes a DFS Namespace including the directory on disk."
+
+ powershell_script "Delete DFS Namespace" do
+ code <<-EOH
+ Remove-DfsnRoot -Path '\\\\#{ENV["COMPUTERNAME"]}\\#{new_resource.namespace_name}' -Force
+ EOH
+ only_if "return ((Get-DfsnRoot -Path '\\\\#{ENV["COMPUTERNAME"]}\\#{new_resource.namespace_name}') -ne $null)"
+ end
+
+ windows_share new_resource.namespace_name do
+ action :delete
+ path file_path
+ end
+
+ directory file_path do
+ action :delete
+ recursive false # I will remove the top level but not any sub levels.
+ end
+ end
+
+ action_class do
+ def file_path
+ "#{new_resource.root}\\#{new_resource.namespace_name}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_dfs_server.rb b/lib/chef/resource/windows_dfs_server.rb
new file mode 100644
index 0000000000..fc161f8189
--- /dev/null
+++ b/lib/chef/resource/windows_dfs_server.rb
@@ -0,0 +1,80 @@
+#
+# Author:: Jason Field
+#
+# Copyright:: 2018, Calastone Ltd.
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class WindowsDfsServer < Chef::Resource
+ unified_mode true
+
+ provides :windows_dfs_server
+
+ description "Use the **windows_dfs_server** resource to set system-wide DFS settings."
+ introduced "15.0"
+
+ property :use_fqdn, [TrueClass, FalseClass],
+ description: "Indicates whether a DFS namespace server uses FQDNs in referrals. If this property is set to true, the server uses FQDNs in referrals. If this property is set to false then the server uses NetBIOS names.",
+ default: false
+
+ property :ldap_timeout_secs, Integer,
+ description: "",
+ default: 30
+
+ property :prefer_login_dc, [TrueClass, FalseClass],
+ description: "",
+ default: false
+
+ property :enable_site_costed_referrals, [TrueClass, FalseClass],
+ description: "",
+ default: false
+
+ property :sync_interval_secs, Integer,
+ description: "",
+ default: 3600
+
+ load_current_value do
+ ps_results = powershell_exec("Get-DfsnServerConfiguration -ComputerName '#{ENV["COMPUTERNAME"]}' | Select LdapTimeoutSec, PreferLogonDC, EnableSiteCostedReferrals, SyncIntervalSec, UseFqdn")
+
+ if ps_results.error?
+ raise "The dfs_server resource failed to fetch the current state via the Get-DfsnServerConfiguration PowerShell cmdlet. Is the DFS Windows feature installed?"
+ end
+
+ Chef::Log.debug("The Get-DfsnServerConfiguration results were #{ps_results.result}")
+ results = ps_results.result
+
+ use_fqdn results["UseFqdn"] || false
+ ldap_timeout_secs results["LdapTimeoutSec"]
+ prefer_login_dc results["PreferLogonDC"] || false
+ enable_site_costed_referrals results["EnableSiteCostedReferrals"] || false
+ sync_interval_secs results["SyncIntervalSec"]
+ end
+
+ action :configure do
+ description "Configure DFS settings."
+
+ converge_if_changed do
+ dfs_cmd = "Set-DfsnServerConfiguration -ComputerName '#{ENV["COMPUTERNAME"]}' -UseFqdn $#{new_resource.use_fqdn} -LdapTimeoutSec #{new_resource.ldap_timeout_secs} -SyncIntervalSec #{new_resource.sync_interval_secs}"
+ dfs_cmd << " -EnableSiteCostedReferrals $#{new_resource.enable_site_costed_referrals}" if new_resource.enable_site_costed_referrals != current_resource.enable_site_costed_referrals
+ dfs_cmd << " -PreferLogonDC $#{new_resource.prefer_login_dc}" if new_resource.prefer_login_dc != current_resource.prefer_login_dc
+ powershell_exec!(dfs_cmd)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_dns_record.rb b/lib/chef/resource/windows_dns_record.rb
new file mode 100644
index 0000000000..329e1a3857
--- /dev/null
+++ b/lib/chef/resource/windows_dns_record.rb
@@ -0,0 +1,95 @@
+#
+# Author:: Jason Field
+#
+# Copyright:: 2018, Calastone Ltd.
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class WindowsDnsRecord < Chef::Resource
+ unified_mode true
+
+ provides :windows_dns_record
+
+ description "The windows_dns_record resource creates a DNS record for the given domain."
+ introduced "15.0"
+
+ property :record_name, String,
+ description: "An optional property to set the dns record name if it differs from the resource block's name.",
+ name_property: true
+
+ property :zone, String,
+ description: "The zone to create the record in.",
+ required: true
+
+ property :target, String,
+ description: "The target for the record.",
+ required: true
+
+ property :record_type, String,
+ description: "The type of record to create, can be either ARecord, CNAME or PTR.",
+ default: "ARecord", equal_to: %w{ARecord CNAME PTR}
+
+ property :dns_server, String,
+ description: "The name of the DNS server on which to create the record.",
+ default: "localhost",
+ introduced: "16.3"
+
+ action :create do
+ description "Creates and updates the DNS entry."
+
+ windows_feature "RSAT-DNS-Server" do
+ not_if new_resource.dns_server.casecmp?("localhost")
+ end
+
+ powershell_package "xDnsServer"
+
+ run_dsc_resource "Present"
+ end
+
+ action :delete do
+ description "Deletes a DNS entry."
+
+ windows_feature "RSAT-DNS-Server" do
+ not_if new_resource.dns_server.casecmp?("localhost")
+ end
+
+ powershell_package "xDnsServer"
+
+ run_dsc_resource "Absent"
+ end
+
+ action_class do
+ private
+
+ # @api private
+ def run_dsc_resource(ensure_prop)
+ dsc_resource "xDnsRecord #{new_resource.record_name}.#{new_resource.zone} #{ensure_prop}" do
+ module_name "xDnsServer"
+ resource :xDnsRecord
+ property :Ensure, ensure_prop
+ property :Name, new_resource.record_name
+ property :Zone, new_resource.zone
+ property :Type, new_resource.record_type
+ property :Target, new_resource.target
+ property :DnsServer, new_resource.dns_server
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_dns_zone.rb b/lib/chef/resource/windows_dns_zone.rb
new file mode 100644
index 0000000000..09555c880c
--- /dev/null
+++ b/lib/chef/resource/windows_dns_zone.rb
@@ -0,0 +1,84 @@
+#
+# Author:: Jason Field
+#
+# Copyright:: 2018, Calastone Ltd.
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class WindowsDnsZone < Chef::Resource
+ unified_mode true
+
+ provides :windows_dns_zone
+
+ description "The windows_dns_zone resource creates an Active Directory Integrated DNS Zone on the local server."
+ introduced "15.0"
+
+ property :zone_name, String,
+ description: "An optional property to set the dns zone name if it differs from the resource block's name.",
+ name_property: true
+
+ property :replication_scope, String,
+ description: "The replication scope for the zone, required if server_type set to 'Domain'.",
+ default: "Domain"
+
+ property :server_type, String,
+ description: "The type of DNS server, Domain or Standalone.",
+ default: "Domain", equal_to: %w{Domain Standalone}
+
+ action :create do
+ description "Creates and updates a DNS Zone."
+
+ powershell_package "xDnsServer"
+
+ run_dsc_resource "Present"
+ end
+
+ action :delete do
+ description "Deletes a DNS Zone."
+
+ powershell_package "xDnsServer"
+
+ run_dsc_resource "Absent"
+ end
+
+ action_class do
+ private
+
+ # @api private
+ def run_dsc_resource(ensure_prop)
+ if new_resource.server_type == "Domain"
+ dsc_resource "xDnsServerADZone #{new_resource.zone_name} #{ensure_prop}" do
+ module_name "xDnsServer"
+ resource :xDnsServerADZone
+ property :Ensure, ensure_prop
+ property :Name, new_resource.zone_name
+ property :ReplicationScope, new_resource.replication_scope
+ end
+ elsif new_resource.server_type == "Standalone"
+ dsc_resource "xDnsServerPrimaryZone #{new_resource.zone_name} #{ensure_prop}" do
+ module_name "xDnsServer"
+ resource :xDnsServerPrimaryZone
+ property :Ensure, ensure_prop
+ property :Name, new_resource.zone_name
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_env.rb b/lib/chef/resource/windows_env.rb
new file mode 100644
index 0000000000..ab65465ed6
--- /dev/null
+++ b/lib/chef/resource/windows_env.rb
@@ -0,0 +1,230 @@
+#
+# Author:: Doug MacEachern (<dougm@vmware.com>)
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright 2010-2016, VMware, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../resource"
+require_relative "../mixin/windows_env_helper"
+
+class Chef
+ class Resource
+ class WindowsEnv < Chef::Resource
+ unified_mode true
+
+ provides :windows_env
+ provides :env # backwards compat with the pre-Chef 14 resource name
+
+ description "Use the **windows_env** resource to manage environment keys in Microsoft Windows. After an environment key is set, Microsoft Windows must be restarted before the environment key will be available to the Task Scheduler."
+ examples <<~DOC
+ **Set an environment variable**:
+
+ ```ruby
+ windows_env 'ComSpec' do
+ value 'C:\\Windows\\system32\\cmd.exe'
+ end
+ ```
+ DOC
+
+ default_action :create
+ allowed_actions :create, :delete, :modify
+
+ property :key_name, String,
+ description: "An optional property to set the name of the key that is to be created, deleted, or modified if it differs from the resource block's name.",
+ name_property: true
+
+ property :value, String,
+ description: "The value of the environmental variable to set.",
+ required: %i{create modify}
+
+ property :delim, [ String, nil, false ],
+ description: "The delimiter that is used to separate multiple values for a single key.",
+ desired_state: false
+
+ property :user, String, default: "<System>"
+
+ action_class do
+ include Chef::Mixin::WindowsEnvHelper
+
+ def whyrun_supported?
+ false
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::WindowsEnv.new(new_resource.name)
+ current_resource.key_name(new_resource.key_name)
+
+ if key_exists?
+ current_resource.value(env_value(new_resource.key_name))
+ else
+ logger.trace("#{new_resource} key does not exist")
+ end
+
+ current_resource
+ end
+
+ def key_exists?
+ @key_exists ||= !!env_value(new_resource.key_name)
+ end
+
+ def requires_modify_or_create?
+ if new_resource.delim
+ # e.g. check for existing value within PATH
+ new_values.inject(0) do |index, val|
+ next_index = current_values.find_index val
+ return true if next_index.nil? || next_index < index
+
+ next_index
+ end
+ false
+ else
+ new_resource.value != current_resource.value
+ end
+ end
+
+ alias_method :compare_value, :requires_modify_or_create?
+
+ # e.g. delete a PATH element
+ #
+ # ==== Returns
+ # <true>:: If we handled the element case and caller should not delete the key
+ # <false>:: Caller should delete the key, either no :delim was specific or value was empty
+ # after we removed the element.
+ def delete_element
+ return false unless new_resource.delim # no delim: delete the key
+
+ needs_delete = new_values.any? { |v| current_values.include?(v) }
+ if !needs_delete
+ logger.trace("#{new_resource} element '#{new_resource.value}' does not exist")
+ true # do not delete the key
+ else
+ new_value =
+ current_values.select do |item|
+ not new_values.include?(item)
+ end.join(new_resource.delim)
+
+ if new_value.empty?
+ false # nothing left here, delete the key
+ else
+ old_value = new_resource.value(new_value)
+ create_env
+ logger.trace("#{new_resource} deleted #{old_value} element")
+ new_resource.updated_by_last_action(true)
+ true # we removed the element and updated; do not delete the key
+ end
+ end
+ end
+
+ def create_env
+ obj = env_obj(@new_resource.key_name)
+ unless obj
+ obj = WIN32OLE.connect("winmgmts://").get("Win32_Environment").spawninstance_
+ obj.name = @new_resource.key_name
+ obj.username = new_resource.user
+ end
+ obj.variablevalue = @new_resource.value
+ obj.put_
+ value = @new_resource.value
+ value = expand_path(value) if @new_resource.key_name.casecmp("PATH") == 0
+ ENV[@new_resource.key_name] = value
+ broadcast_env_change
+ end
+
+ def delete_env
+ obj = env_obj(@new_resource.key_name)
+ if obj
+ obj.delete_
+ broadcast_env_change
+ end
+ if ENV[@new_resource.key_name]
+ ENV.delete(@new_resource.key_name)
+ end
+ end
+
+ def modify_env
+ if new_resource.delim
+ new_resource.value((new_values + current_values).uniq.join(new_resource.delim))
+ end
+ create_env
+ end
+
+ # Returns the current values to split by delimiter
+ def current_values
+ @current_values ||= current_resource.value.split(new_resource.delim)
+ end
+
+ # Returns the new values to split by delimiter
+ def new_values
+ @new_values ||= new_resource.value.split(new_resource.delim)
+ end
+
+ def env_value(key_name)
+ obj = env_obj(key_name)
+ obj.variablevalue if obj
+ end
+
+ def env_obj(key_name)
+ return @env_obj if @env_obj
+
+ wmi = WmiLite::Wmi.new
+ # Note that by design this query is case insensitive with regard to key_name
+ environment_variables = wmi.query("select * from Win32_Environment where name = '#{key_name}'")
+ if environment_variables && environment_variables.length > 0
+ environment_variables.each do |env|
+ @env_obj = env.wmi_ole_object
+ return @env_obj if @env_obj.username.split('\\').last.casecmp(new_resource.user) == 0
+ end
+ end
+ @env_obj = nil
+ end
+ end
+
+ action :create do
+ if key_exists?
+ if requires_modify_or_create?
+ modify_env
+ logger.info("#{new_resource} altered")
+ new_resource.updated_by_last_action(true)
+ end
+ else
+ create_env
+ logger.info("#{new_resource} created")
+ new_resource.updated_by_last_action(true)
+ end
+ end
+
+ action :delete do
+ if ( ENV[new_resource.key_name] || key_exists? ) && !delete_element
+ delete_env
+ logger.info("#{new_resource} deleted")
+ new_resource.updated_by_last_action(true)
+ end
+ end
+
+ action :modify do
+ if key_exists?
+ if requires_modify_or_create?
+ modify_env
+ logger.info("#{new_resource} modified")
+ new_resource.updated_by_last_action(true)
+ end
+ else
+ raise Chef::Exceptions::WindowsEnv, "Cannot modify #{new_resource} - key does not exist!"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_feature.rb b/lib/chef/resource/windows_feature.rb
new file mode 100644
index 0000000000..760a7fe3f1
--- /dev/null
+++ b/lib/chef/resource/windows_feature.rb
@@ -0,0 +1,149 @@
+#
+# Author:: Seth Chisamore (<schisamo@chef.io>)
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class WindowsFeature < Chef::Resource
+ unified_mode true
+
+ provides(:windows_feature) { true }
+
+ description "Use the **windows_feature** resource to add, remove or entirely delete Windows features and roles. This resource calls the 'windows_feature_dism' or 'windows_feature_powershell' resources depending on the specified installation method, and defaults to DISM, which is available on both Workstation and Server editions of Windows."
+ introduced "14.0"
+ examples <<~DOC
+ **Install the DHCP Server feature**:
+
+ ```ruby
+ windows_feature 'DHCPServer' do
+ action :install
+ end
+ ```
+
+ **Install the .Net 3.5.1 feature using repository files on DVD**:
+
+ ```ruby
+ windows_feature "NetFx3" do
+ action :install
+ source 'd:\\sources\\sxs'
+ end
+ ```
+
+ **Remove Telnet Server and Client features**:
+
+ ```ruby
+ windows_feature %w(TelnetServer TelnetClient) do
+ action :remove
+ end
+ ```
+
+ **Add the SMTP Server feature using the PowerShell provider**:
+
+ ```ruby
+ windows_feature 'smtp-server' do
+ action :install
+ all true
+ install_method :windows_feature_powershell
+ end
+ ```
+
+ **Install multiple features using one resource with the PowerShell provider**:
+
+ ```ruby
+ windows_feature %w(Web-Asp-Net45 Web-Net-Ext45) do
+ action :install
+ install_method :windows_feature_powershell
+ end
+ ```
+
+ **Install the Network Policy and Access Service feature, including the management tools**:
+
+ ```ruby
+ windows_feature 'NPAS' do
+ action :install
+ management_tools true
+ install_method :windows_feature_powershell
+ end
+ ```
+ DOC
+
+ property :feature_name, [Array, String],
+ description: "The name of the feature(s) or role(s) to install if they differ from the resource block's name. The same feature may have different names depending on the underlying installation method being used (ie DHCPServer vs DHCP; DNS-Server-Full-Role vs DNS).",
+ name_property: true
+
+ property :source, String,
+ description: "Specify a local repository for the feature install."
+
+ property :all, [TrueClass, FalseClass],
+ description: "Install all sub-features.",
+ default: false
+
+ property :management_tools, [TrueClass, FalseClass],
+ description: "Install all applicable management tools for the roles, role services, or features (PowerShell-only).",
+ default: false
+
+ property :install_method, Symbol,
+ description: "The underlying installation method to use for feature installation. Specify `:windows_feature_dism` for DISM or `:windows_feature_powershell` for PowerShell.",
+ equal_to: %i{windows_feature_dism windows_feature_powershell windows_feature_servermanagercmd},
+ default: :windows_feature_dism
+
+ property :timeout, Integer,
+ description: "Specifies a timeout (in seconds) for the feature installation.",
+ default: 600,
+ desired_state: false
+
+ action :install do
+ description "Install a Windows role/feature"
+
+ run_default_subresource :install
+ end
+
+ action :remove do
+ description "Remove a Windows role/feature"
+
+ run_default_subresource :remove
+ end
+
+ action :delete do
+ description "Remove a Windows role/feature from the image"
+
+ run_default_subresource :delete
+ end
+
+ action_class do
+ private
+
+ # call the appropriate windows_feature resource based on the specified subresource
+ # @return [void]
+ def run_default_subresource(desired_action)
+ raise "Support for Windows feature installation via servermanagercmd.exe has been removed as this support is no longer needed in Windows 2008 R2 and above. You will need to update your recipe to install either via dism or powershell (preferred)." if new_resource.install_method == :windows_feature_servermanagercmd
+
+ declare_resource(new_resource.install_method, new_resource.name) do
+ action desired_action
+ feature_name new_resource.feature_name
+ source new_resource.source if new_resource.source
+ all new_resource.all
+ timeout new_resource.timeout
+ management_tools new_resource.management_tools if new_resource.install_method == :windows_feature_powershell
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_feature_dism.rb b/lib/chef/resource/windows_feature_dism.rb
new file mode 100644
index 0000000000..c9e2f355dc
--- /dev/null
+++ b/lib/chef/resource/windows_feature_dism.rb
@@ -0,0 +1,233 @@
+#
+# Author:: Seth Chisamore (<schisamo@chef.io>)
+#
+# Copyright:: Copyright (c) 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_relative "../resource"
+require_relative "../platform/query_helpers"
+
+class Chef
+ class Resource
+ class WindowsFeatureDism < Chef::Resource
+ unified_mode true
+
+ provides(:windows_feature_dism) { true }
+
+ description "Use the **windows_feature_dism** resource to add, remove, or entirely delete Windows features and roles using DISM."
+ introduced "14.0"
+ examples <<~DOC
+ **Installing the TelnetClient service**:
+
+ ```ruby
+ windows_feature_dism "TelnetClient"
+ ```
+
+ **Installing two features by using an array**:
+
+ ```ruby
+ windows_feature_dism %w(TelnetClient TFTP)
+ ```
+ DOC
+
+ property :feature_name, [Array, String],
+ description: "The name of the feature(s) or role(s) to install if they differ from the resource name.",
+ coerce: proc { |x| to_formatted_array(x) },
+ name_property: true
+
+ property :source, String,
+ description: "Specify a local repository for the feature install."
+
+ property :all, [TrueClass, FalseClass],
+ description: "Install all sub-features. When set to `true`, this is the equivalent of specifying the `/All` switch to `dism.exe`",
+ default: false
+
+ property :timeout, Integer,
+ description: "Specifies a timeout (in seconds) for the feature installation.",
+ default: 600,
+ desired_state: false
+
+ # @return [Array] lowercase the array
+ def to_formatted_array(x)
+ x = x.split(/\s*,\s*/) if x.is_a?(String) # split multiple forms of a comma separated list
+ x.map(&:downcase)
+ end
+
+ action :install do
+ description "Install a Windows role/feature using DISM"
+
+ reload_cached_dism_data unless node["dism_features_cache"]
+ fail_if_unavailable # fail if the features don't exist
+
+ logger.trace("Windows features needing installation: #{features_to_install.empty? ? "none" : features_to_install.join(",")}")
+ unless features_to_install.empty?
+ message = "install Windows feature#{"s" if features_to_install.count > 1} #{features_to_install.join(",")}"
+ converge_by(message) do
+ install_command = "dism.exe /online /enable-feature #{features_to_install.map { |f| "/featurename:#{f}" }.join(" ")} /norestart"
+ install_command << " /LimitAccess /Source:\"#{new_resource.source}\"" if new_resource.source
+ install_command << " /All" if new_resource.all
+ begin
+ shell_out!(install_command, returns: [0, 42, 127, 3010], timeout: new_resource.timeout)
+ rescue Mixlib::ShellOut::ShellCommandFailed => e
+ raise "Error 50 returned by DISM related to parent features, try setting the 'all' property to 'true' on the 'windows_feature_dism' resource." if required_parent_feature?(e.inspect)
+
+ raise e.message
+ end
+
+ reload_cached_dism_data # Reload cached dism feature state
+ end
+ end
+ end
+
+ action :remove do
+ description "Remove a Windows role/feature using DISM"
+
+ reload_cached_dism_data unless node["dism_features_cache"]
+
+ logger.trace("Windows features needing removal: #{features_to_remove.empty? ? "none" : features_to_remove.join(",")}")
+ unless features_to_remove.empty?
+ message = "remove Windows feature#{"s" if features_to_remove.count > 1} #{features_to_remove.join(",")}"
+
+ converge_by(message) do
+ shell_out!("dism.exe /online /disable-feature #{features_to_remove.map { |f| "/featurename:#{f}" }.join(" ")} /norestart", returns: [0, 42, 127, 3010], timeout: new_resource.timeout)
+
+ reload_cached_dism_data # Reload cached dism feature state
+ end
+ end
+ end
+
+ action :delete do
+ description "Remove a Windows role/feature from the image using DISM"
+
+ reload_cached_dism_data unless node["dism_features_cache"]
+
+ fail_if_unavailable # fail if the features don't exist
+
+ logger.trace("Windows features needing deletion: #{features_to_delete.empty? ? "none" : features_to_delete.join(",")}")
+ unless features_to_delete.empty?
+ message = "delete Windows feature#{"s" if features_to_delete.count > 1} #{features_to_delete.join(",")} from the image"
+ converge_by(message) do
+ shell_out!("dism.exe /online /disable-feature #{features_to_delete.map { |f| "/featurename:#{f}" }.join(" ")} /Remove /norestart", returns: [0, 42, 127, 3010], timeout: new_resource.timeout)
+
+ reload_cached_dism_data # Reload cached dism feature state
+ end
+ end
+ end
+
+ action_class do
+ private
+
+ # @return [Array] features the user has requested to install which need installation
+ def features_to_install
+ @install ||= begin
+ # disabled features are always available to install
+ available_for_install = node["dism_features_cache"]["disabled"].dup
+
+ # removed features are also available for installation
+ available_for_install.concat(node["dism_features_cache"]["removed"])
+
+ # the intersection of the features to install & disabled/removed features are what needs installing
+ new_resource.feature_name & available_for_install
+ end
+ end
+
+ # @return [Array] features the user has requested to remove which need removing
+ def features_to_remove
+ # the intersection of the features to remove & enabled features are what needs removing
+ @remove ||= new_resource.feature_name & node["dism_features_cache"]["enabled"]
+ end
+
+ # @return [Array] features the user has requested to delete which need deleting
+ def features_to_delete
+ # the intersection of the features to remove & enabled/disabled features are what needs removing
+ @remove ||= begin
+ all_available = node["dism_features_cache"]["enabled"] +
+ node["dism_features_cache"]["disabled"]
+ new_resource.feature_name & all_available
+ end
+ end
+
+ # if any features are not supported on this release of Windows or
+ # have been deleted raise with a friendly message. At one point in time
+ # we just warned, but this goes against the behavior of ever other package
+ # provider in Chef and it isn't clear what you'd want if you passed an array
+ # and some features were available and others were not.
+ # @return [void]
+ def fail_if_unavailable
+ all_available = node["dism_features_cache"]["enabled"] +
+ node["dism_features_cache"]["disabled"] +
+ node["dism_features_cache"]["removed"]
+
+ # the difference of desired features to install to all features is what's not available
+ unavailable = (new_resource.feature_name - all_available)
+ raise "The Windows feature#{"s" if unavailable.count > 1} #{unavailable.join(",")} #{unavailable.count > 1 ? "are" : "is"} not available on this version of Windows. Run 'dism /online /Get-Features' to see the list of available feature names." unless unavailable.empty?
+ end
+
+ #
+ # FIXME FIXME FIXME
+ # The node object should not be used for caching state like this and this is not a public API and may break.
+ # FIXME FIXME FIXME
+ #
+
+ # run dism.exe to get a list of all available features and their state
+ # and save that to the node at node.override level.
+ # We do this because getting a list of features in dism takes at least a second
+ # and this data will be persisted across multiple resource runs which gives us
+ # a much faster run when no features actually need to be installed / removed.
+ # @return [void]
+ def reload_cached_dism_data
+ logger.trace("Caching Windows features available via dism.exe.")
+ node.override["dism_features_cache"] = Mash.new
+ node.override["dism_features_cache"]["enabled"] = []
+ node.override["dism_features_cache"]["disabled"] = []
+ node.override["dism_features_cache"]["removed"] = []
+
+ # Grab raw feature information from dism command line
+ raw_list_of_features = shell_out("dism.exe /Get-Features /Online /Format:Table /English").stdout
+
+ # Split stdout into an array by windows line ending
+ features_list = raw_list_of_features.split("\r\n")
+ features_list.each do |feature_details_raw|
+ case feature_details_raw
+ when /Payload Removed/ # matches 'Disabled with Payload Removed'
+ add_to_feature_mash("removed", feature_details_raw)
+ when /Enable/ # matches 'Enabled' and 'Enable Pending' aka after reboot
+ add_to_feature_mash("enabled", feature_details_raw)
+ when /Disable/ # matches 'Disabled' and 'Disable Pending' aka after reboot
+ add_to_feature_mash("disabled", feature_details_raw)
+ end
+ end
+ logger.trace("The cache contains\n#{node["dism_features_cache"]}")
+ end
+
+ # parse the feature string and add the values to the appropriate array in the strips
+ # trailing whitespace characters then split on n number of spaces + | + n number of spaces
+ # @return [void]
+ def add_to_feature_mash(feature_type, feature_string)
+ feature_details = feature_string.strip.split(/\s+[|]\s+/).first
+
+ # dism isn't case sensitive so it's best to compare lowercase lists so the
+ # user input doesn't need to be case sensitive
+ feature_details.downcase!
+ node.override["dism_features_cache"][feature_type] << feature_details
+ end
+
+ def required_parent_feature?(error_message)
+ error_message.include?("Error: 50") && error_message.include?("required parent feature")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_feature_powershell.rb b/lib/chef/resource/windows_feature_powershell.rb
new file mode 100644
index 0000000000..735ed080ff
--- /dev/null
+++ b/lib/chef/resource/windows_feature_powershell.rb
@@ -0,0 +1,242 @@
+#
+# Author:: Greg Zapp (<greg.zapp@gmail.com>)
+#
+# Copyright:: Copyright (c) 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_relative "../json_compat"
+require_relative "../resource"
+require_relative "../platform/query_helpers"
+
+class Chef
+ class Resource
+ class WindowsFeaturePowershell < Chef::Resource
+ unified_mode true
+
+ provides(:windows_feature_powershell) { true }
+
+ description "Use the **windows_feature_powershell** resource to add, remove, or entirely delete Windows features and roles using PowerShell. This resource offers significant speed benefits over the windows_feature_dism resource, but requires installation of the Remote Server Administration Tools on non-server releases of Windows."
+ introduced "14.0"
+ examples <<~DOC
+ **Add the SMTP Server feature**:
+
+ ```ruby
+ windows_feature_powershell "smtp-server" do
+ action :install
+ all true
+ end
+ ```
+
+ **Install multiple features using one resource**:
+
+ ```ruby
+ windows_feature_powershell ['Web-Asp-Net45', 'Web-Net-Ext45'] do
+ action :install
+ end
+ ```
+
+ **Install the Network Policy and Access Service feature**:
+
+ ```ruby
+ windows_feature_powershell 'NPAS' do
+ action :install
+ management_tools true
+ end
+ ```
+ DOC
+
+ property :feature_name, [Array, String],
+ description: "The name of the feature(s) or role(s) to install if they differ from the resource block's name.",
+ coerce: proc { |x| to_formatted_array(x) },
+ name_property: true
+
+ property :source, String,
+ description: "Specify a local repository for the feature install."
+
+ property :all, [TrueClass, FalseClass],
+ description: "Install all subfeatures. When set to `true`, this is the equivalent of specifying the `-InstallAllSubFeatures` switch with `Add-WindowsFeature`.",
+ default: false
+
+ property :timeout, Integer,
+ description: "Specifies a timeout (in seconds) for the feature installation.",
+ default: 600,
+ desired_state: false
+
+ property :management_tools, [TrueClass, FalseClass],
+ description: "Install all applicable management tools for the roles, role services, or features.",
+ default: false
+
+ # Converts strings of features into an Array. Array objects are lowercased
+ # @return [Array] array of features
+ def to_formatted_array(x)
+ x = x.split(/\s*,\s*/) if x.is_a?(String) # split multiple forms of a comma separated list
+
+ # features aren't case sensitive so let's compare in lowercase
+ x.map(&:downcase)
+ end
+
+ action :install do
+ reload_cached_powershell_data unless node["powershell_features_cache"]
+ fail_if_unavailable # fail if the features don't exist
+ fail_if_removed # fail if the features are in removed state
+
+ Chef::Log.debug("Windows features needing installation: #{features_to_install.empty? ? "none" : features_to_install.join(",")}")
+ unless features_to_install.empty?
+ converge_by("install Windows feature#{"s" if features_to_install.count > 1} #{features_to_install.join(",")}") do
+ install_command = "Install-WindowsFeature #{features_to_install.join(",")}"
+ install_command << " -IncludeAllSubFeature" if new_resource.all
+ install_command << " -Source \"#{new_resource.source}\"" if new_resource.source
+ install_command << " -IncludeManagementTools" if new_resource.management_tools
+
+ cmd = powershell_out!(install_command, timeout: new_resource.timeout)
+ Chef::Log.info(cmd.stdout)
+
+ reload_cached_powershell_data # Reload cached powershell feature state
+ end
+ end
+ end
+
+ action :remove do
+ reload_cached_powershell_data unless node["powershell_features_cache"]
+
+ Chef::Log.debug("Windows features needing removal: #{features_to_remove.empty? ? "none" : features_to_remove.join(",")}")
+
+ unless features_to_remove.empty?
+ converge_by("remove Windows feature#{"s" if features_to_remove.count > 1} #{features_to_remove.join(",")}") do
+ cmd = powershell_out!("Uninstall-WindowsFeature #{features_to_remove.join(",")}", timeout: new_resource.timeout)
+ Chef::Log.info(cmd.stdout)
+
+ reload_cached_powershell_data # Reload cached powershell feature state
+ end
+ end
+ end
+
+ action :delete do
+ reload_cached_powershell_data unless node["powershell_features_cache"]
+
+ fail_if_unavailable # fail if the features don't exist
+
+ Chef::Log.debug("Windows features needing deletion: #{features_to_delete.empty? ? "none" : features_to_delete.join(",")}")
+
+ unless features_to_delete.empty?
+ converge_by("delete Windows feature#{"s" if features_to_delete.count > 1} #{features_to_delete.join(",")} from the image") do
+ cmd = powershell_out!("Uninstall-WindowsFeature #{features_to_delete.join(",")} -Remove", timeout: new_resource.timeout)
+ Chef::Log.info(cmd.stdout)
+
+ reload_cached_powershell_data # Reload cached powershell feature state
+ end
+ end
+ end
+
+ action_class do
+ # @return [Array] features the user has requested to install which need installation
+ def features_to_install
+ # the intersection of the features to install & disabled/removed features are what needs installing
+ @features_to_install ||= begin
+ features = node["powershell_features_cache"]["disabled"]
+ features |= node["powershell_features_cache"]["removed"] if new_resource.source
+ new_resource.feature_name & features
+ end
+ end
+
+ # @return [Array] features the user has requested to remove which need removing
+ def features_to_remove
+ # the intersection of the features to remove & enabled features are what needs removing
+ @remove ||= new_resource.feature_name & node["powershell_features_cache"]["enabled"]
+ end
+
+ # @return [Array] features the user has requested to delete which need deleting
+ def features_to_delete
+ # the intersection of the features to remove & enabled/disabled features are what needs removing
+ @remove ||= begin
+ all_available = node["powershell_features_cache"]["enabled"] +
+ node["powershell_features_cache"]["disabled"]
+ new_resource.feature_name & all_available
+ end
+ end
+
+ # if any features are not supported on this release of Windows or
+ # have been deleted raise with a friendly message. At one point in time
+ # we just warned, but this goes against the behavior of ever other package
+ # provider in Chef and it isn't clear what you'd want if you passed an array
+ # and some features were available and others were not.
+ # @return [void]
+ def fail_if_unavailable
+ all_available = node["powershell_features_cache"]["enabled"] +
+ node["powershell_features_cache"]["disabled"] +
+ node["powershell_features_cache"]["removed"]
+
+ # the difference of desired features to install to all features is what's not available
+ unavailable = (new_resource.feature_name - all_available)
+ raise "The Windows feature#{"s" if unavailable.count > 1} #{unavailable.join(",")} #{unavailable.count > 1 ? "are" : "is"} not available on this version of Windows. Run 'Get-WindowsFeature' to see the list of available feature names." unless unavailable.empty?
+ end
+
+ # run Get-WindowsFeature to get a list of all available features and their state
+ # and save that to the node at node.override level.
+ # @return [void]
+ def reload_cached_powershell_data
+ Chef::Log.debug("Caching Windows features available via Get-WindowsFeature.")
+
+ #
+ # FIXME FIXME FIXME
+ # The node object should not be used for caching state like this and this is not a public API and may break.
+ # FIXME FIXME FIXME
+ #
+ node.override["powershell_features_cache"] = Mash.new
+ node.override["powershell_features_cache"]["enabled"] = []
+ node.override["powershell_features_cache"]["disabled"] = []
+ node.override["powershell_features_cache"]["removed"] = []
+
+ parsed_feature_list.each do |feature_details_raw|
+ case feature_details_raw["InstallState"]
+ when 5 # matches 'Removed' InstallState
+ add_to_feature_mash("removed", feature_details_raw["Name"])
+ when 1, 3 # matches 'Installed' or 'InstallPending' states
+ add_to_feature_mash("enabled", feature_details_raw["Name"])
+ when 0, 2 # matches 'Available' or 'UninstallPending' states
+ add_to_feature_mash("disabled", feature_details_raw["Name"])
+ end
+ end
+ Chef::Log.debug("The powershell cache contains\n#{node["powershell_features_cache"]}")
+ end
+
+ # fetch the list of available feature names and state in JSON and parse the JSON
+ def parsed_feature_list
+ # Grab raw feature information from WindowsFeature
+ raw_list_of_features = powershell_out!("Get-WindowsFeature | Select-Object -Property Name,InstallState | ConvertTo-Json -Compress", timeout: new_resource.timeout).stdout
+
+ Chef::JSONCompat.from_json(raw_list_of_features)
+ end
+
+ # add the features values to the appropriate array
+ # @return [void]
+ def add_to_feature_mash(feature_type, feature_details)
+ # add the lowercase feature name to the mash so we can compare it lowercase later
+ node.override["powershell_features_cache"][feature_type] << feature_details.downcase
+ end
+
+ # Fail if any of the packages are in a removed state
+ # @return [void]
+ def fail_if_removed
+ return if new_resource.source # if someone provides a source then all is well
+ return if registry_key_exists?('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Servicing') && registry_value_exists?('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Servicing', name: "LocalSourcePath") # if source is defined in the registry, still fine
+
+ removed = new_resource.feature_name & node["powershell_features_cache"]["removed"]
+ raise "The Windows feature#{"s" if removed.count > 1} #{removed.join(",")} #{removed.count > 1 ? "are" : "is"} removed from the host and cannot be installed." unless removed.empty?
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_firewall_profile.rb b/lib/chef/resource/windows_firewall_profile.rb
new file mode 100644
index 0000000000..ada9729699
--- /dev/null
+++ b/lib/chef/resource/windows_firewall_profile.rb
@@ -0,0 +1,196 @@
+#
+# Author:: John McCrae (<jmccrae@chef.io>)
+# Author:: Davin Taddeo (<davin@chef.io>)
+# Copyright:: Copyright (c) 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.
+#
+
+class Chef
+ class Resource
+ class WindowsFirewallProfile < Chef::Resource
+ provides :windows_firewall_profile
+ description "Use the **windows_firewall_profile** resource to enable, disable, and configure the Windows firewall."
+ introduced "16.3"
+
+ examples <<~DOC
+ **Enable and Configure the Private Profile of the Windows Profile**:
+
+ ```ruby
+ windows_firewall_profile 'Private' do
+ default_inbound_action 'Block'
+ default_outbound_action 'Allow'
+ allow_inbound_rules true
+ display_notification false
+ action :enable
+ end
+ ```
+
+ **Enable and Configure the Public Profile of the Windows Firewall**:
+
+ ```ruby
+ windows_firewall_profile 'Public' do
+ default_inbound_action 'Block'
+ default_outbound_action 'Allow'
+ allow_inbound_rules false
+ display_notification false
+ action :enable
+ end
+ ```
+
+ **Disable the Domain Profile of the Windows Firewall**:
+
+ ```ruby
+ windows_firewall_profile 'Disable the Domain Profile of the Windows Firewall' do
+ profile 'Domain'
+ action :disable
+ end
+ ```
+ DOC
+
+ unified_mode true
+
+ property :profile, String,
+ name_property: true,
+ equal_to: %w{ Domain Public Private },
+ description: "Set the Windows Profile being configured"
+
+ property :default_inbound_action, [String, nil],
+ equal_to: %w{ Allow Block NotConfigured },
+ description: "Set the default policy for inbound network traffic"
+
+ property :default_outbound_action, [String, nil],
+ equal_to: %w{ Allow Block NotConfigured },
+ description: "Set the default policy for outbound network traffic"
+
+ property :allow_inbound_rules, [true, false, String], equal_to: [true, false, "NotConfigured"], description: "Allow users to set inbound firewall rules"
+ property :allow_local_firewall_rules, [true, false, String], equal_to: [true, false, "NotConfigured"], description: "Merges inbound firewall rules into the policy"
+ property :allow_local_ipsec_rules, [true, false, String], equal_to: [true, false, "NotConfigured"], description: "Allow users to manage local connection security rules"
+ property :allow_user_apps, [true, false, String], equal_to: [true, false, "NotConfigured"], description: "Allow user applications to manage firewall"
+ property :allow_user_ports, [true, false, String], equal_to: [true, false, "NotConfigured"], description: "Allow users to manage firewall port rules"
+ property :allow_unicast_response, [true, false, String], equal_to: [true, false, "NotConfigured"], description: "Allow unicast responses to multicast and broadcast messages"
+ property :display_notification, [true, false, String], equal_to: [true, false, "NotConfigured"], description: "Display a notification when firewall blocks certain activity"
+
+ load_current_value do |desired|
+ ps_get_net_fw_profile = load_firewall_state(desired.profile)
+ output = powershell_exec(ps_get_net_fw_profile)
+ if output.result.empty?
+ current_value_does_not_exist!
+ else
+ state = output.result
+ end
+
+ default_inbound_action state["default_inbound_action"]
+ default_outbound_action state["default_outbound_action"]
+ allow_inbound_rules convert_to_ruby(state["allow_inbound_rules"])
+ allow_local_firewall_rules convert_to_ruby(state["allow_local_firewall_rules"])
+ allow_local_ipsec_rules convert_to_ruby(state["allow_local_ipsec_rules"])
+ allow_user_apps convert_to_ruby(state["allow_user_apps"])
+ allow_user_ports convert_to_ruby(state["allow_user_ports"])
+ allow_unicast_response convert_to_ruby(state["allow_unicast_response"])
+ display_notification convert_to_ruby(state["display_notification"])
+ end
+
+ def convert_to_ruby(obj)
+ if obj.to_s.downcase == "true"
+ true
+ elsif obj.to_s.downcase == "false"
+ false
+ elsif obj.to_s.downcase == "notconfigured"
+ "NotConfigured"
+ end
+ end
+
+ def convert_to_powershell(obj)
+ if obj.to_s.downcase == "true"
+ "True"
+ elsif obj.to_s.downcase == "false"
+ "False"
+ elsif obj.to_s.downcase == "notconfigured"
+ "NotConfigured"
+ end
+ end
+
+ action :enable do
+ converge_if_changed :default_inbound_action, :default_outbound_action, :allow_inbound_rules, :allow_local_firewall_rules,
+ :allow_local_ipsec_rules, :allow_user_apps, :allow_user_ports, :allow_unicast_response, :display_notification do
+ fw_cmd = firewall_command(new_resource.profile)
+ powershell_exec!(fw_cmd)
+ end
+ unless firewall_enabled?(new_resource.profile)
+ converge_by "Enable the #{new_resource.profile} Firewall Profile" do
+ cmd = "Set-NetFirewallProfile -Profile #{new_resource.profile} -Enabled \"True\""
+ powershell_exec!(cmd)
+ end
+ end
+ end
+
+ action :disable do
+ if firewall_enabled?(new_resource.profile)
+ converge_by "Disable the #{new_resource.profile} Firewall Profile" do
+ cmd = "Set-NetFirewallProfile -Profile #{new_resource.profile} -Enabled \"False\""
+ powershell_exec!(cmd)
+ end
+ end
+ end
+
+ action_class do
+ def firewall_command(fw_profile)
+ cmd = "Set-NetFirewallProfile -Profile \"#{fw_profile}\""
+ cmd << " -DefaultInboundAction \"#{new_resource.default_inbound_action}\"" unless new_resource.default_inbound_action.nil?
+ cmd << " -DefaultOutboundAction \"#{new_resource.default_outbound_action}\"" unless new_resource.default_outbound_action.nil?
+ cmd << " -AllowInboundRules \"#{convert_to_powershell(new_resource.allow_inbound_rules)}\"" unless new_resource.allow_inbound_rules.nil?
+ cmd << " -AllowLocalFirewallRules \"#{convert_to_powershell(new_resource.allow_local_firewall_rules)}\"" unless new_resource.allow_local_firewall_rules.nil?
+ cmd << " -AllowLocalIPsecRules \"#{convert_to_powershell(new_resource.allow_local_ipsec_rules)}\"" unless new_resource.allow_local_ipsec_rules.nil?
+ cmd << " -AllowUserApps \"#{convert_to_powershell(new_resource.allow_user_apps)}\"" unless new_resource.allow_user_apps.nil?
+ cmd << " -AllowUserPorts \"#{convert_to_powershell(new_resource.allow_user_ports)}\"" unless new_resource.allow_user_ports.nil?
+ cmd << " -AllowUnicastResponseToMulticast \"#{convert_to_powershell(new_resource.allow_unicast_response)}\"" unless new_resource.allow_unicast_response.nil?
+ cmd << " -NotifyOnListen \"#{convert_to_powershell(new_resource.display_notification)}\"" unless new_resource.display_notification.nil?
+ cmd
+ end
+
+ def firewall_enabled?(profile_name)
+ cmd = <<~CODE
+ $#{profile_name} = Get-NetFirewallProfile -Profile #{profile_name}
+ if ($#{profile_name}.Enabled) {
+ return $true
+ } else {return $false}
+ CODE
+ powershell_exec!(cmd).result
+ end
+ end
+
+ private
+
+ # build the command to load the current resource
+ # @return [String] current firewall state
+ def load_firewall_state(profile_name)
+ <<-EOH
+ Remove-TypeData System.Array # workaround for PS bug here: https://bit.ly/2SRMQ8M
+ $#{profile_name} = Get-NetFirewallProfile -Profile #{profile_name}
+ ([PSCustomObject]@{
+ default_inbound_action = $#{profile_name}.DefaultInboundAction.ToString()
+ default_outbound_action = $#{profile_name}.DefaultOutboundAction.ToString()
+ allow_inbound_rules = $#{profile_name}.AllowInboundRules.ToString()
+ allow_local_firewall_rules = $#{profile_name}.AllowLocalFirewallRules.ToString()
+ allow_local_ipsec_rules = $#{profile_name}.AllowLocalIPsecRules.ToString()
+ allow_user_apps = $#{profile_name}.AllowUserApps.ToString()
+ allow_user_ports = $#{profile_name}.AllowUserPorts.ToString()
+ allow_unicast_response = $#{profile_name}.AllowUnicastResponseToMulticast.ToString()
+ display_notification = $#{profile_name}.NotifyOnListen.ToString()
+ })
+ EOH
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_firewall_rule.rb b/lib/chef/resource/windows_firewall_rule.rb
new file mode 100644
index 0000000000..a6f0614362
--- /dev/null
+++ b/lib/chef/resource/windows_firewall_rule.rb
@@ -0,0 +1,326 @@
+# Author:: Matt Clifton (spartacus003@hotmail.com)
+# Author:: Matt Stratton (matt.stratton@gmail.com)
+# Author:: Tor Magnus Rakvåg (tor.magnus@outlook.com)
+# Author:: Tim Smith (tsmith@chef.io)
+# Copyright:: 2013-2015 Matt Clifton
+# Copyright:: Copyright (c) Chef Software Inc.
+# Copyright:: 2018, Intility AS
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ class Resource
+ class WindowsFirewallRule < Chef::Resource
+ unified_mode true
+
+ provides :windows_firewall_rule
+
+ description "Use the **windows_firewall_rule** resource to create, change or remove Windows firewall rules."
+ introduced "14.7"
+ examples <<~DOC
+ **Allowing port 80 access**:
+
+ ```ruby
+ windows_firewall_rule 'IIS' do
+ local_port '80'
+ protocol 'TCP'
+ firewall_action :allow
+ end
+ ```
+
+ **Allow protocol ICMPv6 with ICMP Type**:
+
+ ```ruby
+ windows_firewall_rule 'CoreNet-Rule' do
+ rule_name 'CoreNet-ICMP6-LR2-In'
+ display_name 'Core Networking - Multicast Listener Report v2 (ICMPv6-In)'
+ local_port 'RPC'
+ protocol 'ICMPv6'
+ icmp_type '8'
+ end
+ ```
+
+ **Blocking WinRM over HTTP on a particular IP**:
+
+ ```ruby
+ windows_firewall_rule 'Disable WinRM over HTTP' do
+ local_port '5985'
+ protocol 'TCP'
+ firewall_action :block
+ local_address '192.168.1.1'
+ end
+ ```
+
+ **Deleting an existing rule**
+
+ ```ruby
+ windows_firewall_rule 'Remove the SSH rule' do
+ rule_name 'ssh'
+ action :delete
+ end
+ ```
+ DOC
+
+ property :rule_name, String,
+ name_property: true,
+ description: "An optional property to set the name of the firewall rule to assign if it differs from the resource block's name."
+
+ property :description, String,
+ description: "The description to assign to the firewall rule."
+
+ property :displayname, String,
+ description: "The displayname to assign to the firewall rule.",
+ default: lazy { rule_name },
+ default_description: "The rule_name property value.",
+ introduced: "16.0"
+
+ property :group, String,
+ description: "Specifies that only matching firewall rules of the indicated group association are copied.",
+ introduced: "16.0"
+
+ property :local_address, String,
+ description: "The local address the firewall rule applies to."
+
+ property :local_port, [String, Integer, Array],
+ # split various formats of comma separated lists and provide a sorted array of strings to match PS output
+ coerce: proc { |d| d.is_a?(String) ? d.split(/\s*,\s*/).sort : Array(d).sort.map(&:to_s) },
+ description: "The local port the firewall rule applies to."
+
+ property :remote_address, String,
+ description: "The remote address the firewall rule applies to."
+
+ property :remote_port, [String, Integer, Array],
+ # split various formats of comma separated lists and provide a sorted array of strings to match PS output
+ coerce: proc { |d| d.is_a?(String) ? d.split(/\s*,\s*/).sort : Array(d).sort.map(&:to_s) },
+ description: "The remote port the firewall rule applies to."
+
+ property :direction, [Symbol, String],
+ default: :inbound, equal_to: %i{inbound outbound},
+ description: "The direction of the firewall rule. Direction means either inbound or outbound traffic.",
+ coerce: proc { |d| d.is_a?(String) ? d.downcase.to_sym : d }
+
+ property :protocol, String,
+ default: "TCP",
+ description: "The protocol the firewall rule applies to."
+
+ property :icmp_type, [String, Integer],
+ description: "Specifies the ICMP Type parameter for using a protocol starting with ICMP",
+ default: "Any",
+ introduced: "16.0"
+
+ property :firewall_action, [Symbol, String],
+ default: :allow, equal_to: %i{allow block notconfigured},
+ description: "The action of the firewall rule.",
+ coerce: proc { |f| f.is_a?(String) ? f.downcase.to_sym : f }
+
+ property :profile, [Symbol, String, Array],
+ default: :any,
+ description: "The profile the firewall rule applies to.",
+ coerce: proc { |p| Array(p).map(&:downcase).map(&:to_sym).sort },
+ callbacks: {
+ "contains values not in :public, :private, :domain, :any or :notapplicable" => lambda { |p|
+ p.all? { |e| %i{public private domain any notapplicable}.include?(e) }
+ },
+ }
+
+ property :program, String,
+ description: "The program the firewall rule applies to."
+
+ property :service, String,
+ description: "The service the firewall rule applies to."
+
+ property :interface_type, [Symbol, String],
+ default: :any, equal_to: %i{any wireless wired remoteaccess},
+ description: "The interface type the firewall rule applies to.",
+ coerce: proc { |i| i.is_a?(String) ? i.downcase.to_sym : i }
+
+ property :enabled, [TrueClass, FalseClass],
+ default: true,
+ description: "Whether or not to enable the firewall rule."
+
+ alias_method :localip, :local_address
+ alias_method :remoteip, :remote_address
+ alias_method :localport, :local_port
+ alias_method :remoteport, :remote_port
+ alias_method :interfacetype, :interface_type
+
+ load_current_value do
+ load_state_cmd = load_firewall_state(rule_name)
+ output = powershell_exec(load_state_cmd)
+ if output.result.empty?
+ current_value_does_not_exist!
+ else
+ state = output.result
+ end
+
+ # Need to reverse `$rule.Profile.ToString()` in powershell command
+ current_profiles = state["profile"].split(", ").map(&:to_sym)
+
+ description state["description"]
+ displayname state["displayname"]
+ group state["group"]
+ local_address state["local_address"]
+ local_port Array(state["local_port"]).sort
+ remote_address state["remote_address"]
+ remote_port Array(state["remote_port"]).sort
+ direction state["direction"]
+ protocol state["protocol"]
+ icmp_type state["icmp_type"]
+ firewall_action state["firewall_action"]
+ profile current_profiles
+ program state["program"]
+ service state["service"]
+ interface_type state["interface_type"]
+ enabled state["enabled"]
+ end
+
+ action :create do
+ description "Create a Windows firewall entry."
+ if current_resource
+ converge_if_changed :rule_name, :description, :displayname, :local_address, :local_port, :remote_address,
+ :remote_port, :direction, :protocol, :icmp_type, :firewall_action, :profile, :program, :service,
+ :interface_type, :enabled do
+ cmd = firewall_command("Set")
+ powershell_exec!(cmd)
+ end
+ converge_if_changed :group do
+ powershell_exec!("Remove-NetFirewallRule -Name '#{new_resource.rule_name}'")
+ cmd = firewall_command("New")
+ powershell_exec!(cmd)
+ end
+ else
+ converge_by("create firewall rule #{new_resource.rule_name}") do
+ cmd = firewall_command("New")
+ powershell_exec!(cmd)
+ end
+ end
+ end
+
+ action :delete do
+ description "Delete an existing Windows firewall entry."
+
+ if current_resource
+ converge_by("delete firewall rule #{new_resource.rule_name}") do
+ powershell_exec!("Remove-NetFirewallRule -Name '#{new_resource.rule_name}'")
+ end
+ else
+ Chef::Log.info("Firewall rule \"#{new_resource.rule_name}\" doesn't exist. Skipping.")
+ end
+ end
+
+ action_class do
+ # build the command to create a firewall rule based on new_resource values
+ # @return [String] firewall create command
+ def firewall_command(cmdlet_type)
+ cmd = "#{cmdlet_type}-NetFirewallRule -Name '#{new_resource.rule_name}'"
+ cmd << " -DisplayName '#{new_resource.displayname}'" if new_resource.displayname && cmdlet_type == "New"
+ cmd << " -NewDisplayName '#{new_resource.displayname}'" if new_resource.displayname && cmdlet_type == "Set"
+ cmd << " -Group '#{new_resource.group}'" if new_resource.group && cmdlet_type == "New"
+ cmd << " -Description '#{new_resource.description}'" if new_resource.description
+ cmd << " -LocalAddress '#{new_resource.local_address}'" if new_resource.local_address
+ cmd << " -LocalPort '#{new_resource.local_port.join("', '")}'" if new_resource.local_port
+ cmd << " -RemoteAddress '#{new_resource.remote_address}'" if new_resource.remote_address
+ cmd << " -RemotePort '#{new_resource.remote_port.join("', '")}'" if new_resource.remote_port
+ cmd << " -Direction '#{new_resource.direction}'" if new_resource.direction
+ cmd << " -Protocol '#{new_resource.protocol}'" if new_resource.protocol
+ cmd << " -IcmpType '#{new_resource.icmp_type}'"
+ cmd << " -Action '#{new_resource.firewall_action}'" if new_resource.firewall_action
+ cmd << " -Profile '#{new_resource.profile.join("', '")}'" if new_resource.profile
+ cmd << " -Program '#{new_resource.program}'" if new_resource.program
+ cmd << " -Service '#{new_resource.service}'" if new_resource.service
+ cmd << " -InterfaceType '#{new_resource.interface_type}'" if new_resource.interface_type
+ cmd << " -Enabled '#{new_resource.enabled}'"
+
+ cmd
+ end
+
+ def define_resource_requirements
+ requirements.assert(:create) do |a|
+ a.assertion do
+ if new_resource.icmp_type.is_a?(String)
+ !new_resource.icmp_type.empty?
+ elsif new_resource.icmp_type.is_a?(Integer)
+ !new_resource.icmp_type.nil?
+ end
+ end
+ a.failure_message("The :icmp_type property can not be empty in #{new_resource.rule_name}")
+ end
+
+ requirements.assert(:create) do |a|
+ a.assertion do
+ if new_resource.icmp_type.is_a?(Integer)
+ new_resource.protocol.start_with?("ICMP")
+ elsif new_resource.icmp_type.is_a?(String) && !new_resource.protocol.start_with?("ICMP")
+ new_resource.icmp_type == "Any"
+ else
+ true
+ end
+ end
+ a.failure_message("The :icmp_type property has a value of #{new_resource.icmp_type} set, but is not allowed for :protocol #{new_resource.protocol} in #{new_resource.rule_name}")
+ end
+
+ requirements.assert(:create) do |a|
+ a.assertion do
+ if new_resource.icmp_type.is_a?(Integer)
+ (0..255).cover?(new_resource.icmp_type)
+ elsif new_resource.icmp_type.is_a?(String) && !new_resource.icmp_type.include?(":") && new_resource.protocol.start_with?("ICMP")
+ (0..255).cover?(new_resource.icmp_type.to_i)
+ elsif new_resource.icmp_type.is_a?(String) && new_resource.icmp_type.include?(":") && new_resource.protocol.start_with?("ICMP")
+ new_resource.icmp_type.split(":").all? { |type| (0..255).cover?(type.to_i) }
+ else
+ true
+ end
+ end
+ a.failure_message("Can not set :icmp_type to #{new_resource.icmp_type} as one value is out of range (0 to 255) in #{new_resource.rule_name}")
+ end
+ end
+ end
+
+ private
+
+ # build the command to load the current resource
+ # @return [String] current firewall state
+ def load_firewall_state(rule_name)
+ <<-EOH
+ Remove-TypeData System.Array # workaround for PS bug here: https://bit.ly/2SRMQ8M
+ $rule = Get-NetFirewallRule -Name '#{rule_name}'
+ $addressFilter = $rule | Get-NetFirewallAddressFilter
+ $portFilter = $rule | Get-NetFirewallPortFilter
+ $applicationFilter = $rule | Get-NetFirewallApplicationFilter
+ $serviceFilter = $rule | Get-NetFirewallServiceFilter
+ $interfaceTypeFilter = $rule | Get-NetFirewallInterfaceTypeFilter
+ ([PSCustomObject]@{
+ rule_name = $rule.Name
+ description = $rule.Description
+ displayname = $rule.DisplayName
+ group = $rule.Group
+ local_address = $addressFilter.LocalAddress
+ local_port = $portFilter.LocalPort
+ remote_address = $addressFilter.RemoteAddress
+ remote_port = $portFilter.RemotePort
+ direction = $rule.Direction.ToString()
+ protocol = $portFilter.Protocol
+ icmp_type = $portFilter.IcmpType
+ firewall_action = $rule.Action.ToString()
+ profile = $rule.Profile.ToString()
+ program = $applicationFilter.Program
+ service = $serviceFilter.Service
+ interface_type = $interfaceTypeFilter.InterfaceType.ToString()
+ enabled = [bool]::Parse($rule.Enabled.ToString())
+ })
+ EOH
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_font.rb b/lib/chef/resource/windows_font.rb
new file mode 100644
index 0000000000..c9128aa4b0
--- /dev/null
+++ b/lib/chef/resource/windows_font.rb
@@ -0,0 +1,135 @@
+#
+# Copyright:: 2014-2018, Schuberg Philis BV.
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class WindowsFont < Chef::Resource
+ require_relative "../util/path_helper"
+ unified_mode true
+
+ provides(:windows_font) { true }
+
+ description "Use the **windows_font** resource to install font files on Windows. By default, the font is sourced from the cookbook using the resource, but a URI source can be specified as well."
+ introduced "14.0"
+ examples <<~DOC
+ **Install a font from a https source**:
+
+ ```ruby
+ windows_font 'Custom.otf' do
+ source 'https://example.com/Custom.otf'
+ end
+ ```
+ DOC
+
+ property :font_name, String,
+ description: "An optional property to set the name of the font to install if it differs from the resource block's name.",
+ name_property: true
+
+ property :source, String,
+ description: "A local filesystem path or URI that is used to source the font file.",
+ coerce: proc { |x| /^.:.*/.match?(x) ? x.tr('\\', "/").gsub("//", "/") : x }
+
+ action :install do
+ description "Install a font to the system fonts directory."
+
+ if font_exists?
+ logger.trace("Not installing font: #{new_resource.font_name} as font already installed.")
+ else
+ retrieve_cookbook_font
+ install_font
+ del_cookbook_font
+ end
+ end
+
+ action_class do
+ # if a source is specified fetch using remote_file. If not use cookbook_file
+ def retrieve_cookbook_font
+ font_file = new_resource.font_name
+ if new_resource.source
+ declare_resource(:remote_file, font_file) do
+ action :nothing
+ source source_uri
+ path Chef::Util::PathHelper.join(ENV["TEMP"], font_file)
+ end.run_action(:create)
+ else
+ declare_resource(:cookbook_file, font_file) do
+ action :nothing
+ cookbook cookbook_name.to_s unless cookbook_name.nil?
+ path Chef::Util::PathHelper.join(ENV["TEMP"], font_file)
+ end.run_action(:create)
+ end
+ end
+
+ # delete the temp cookbook file
+ def del_cookbook_font
+ file Chef::Util::PathHelper.join(ENV["TEMP"], new_resource.font_name) do
+ action :delete
+ end
+ end
+
+ # install the font into the appropriate fonts directory
+ def install_font
+ require "win32ole" if RUBY_PLATFORM.match?(/mswin|mingw32|windows/)
+ fonts_dir = Chef::Util::PathHelper.join(ENV["windir"], "fonts")
+ folder = WIN32OLE.new("Shell.Application").Namespace(fonts_dir)
+ converge_by("install font #{new_resource.font_name} to #{fonts_dir}") do
+ folder.CopyHere(Chef::Util::PathHelper.join(ENV["TEMP"], new_resource.font_name))
+ end
+ end
+
+ # Check to see if the font is installed in the fonts dir
+ #
+ # @return [Boolean] Is the font is installed?
+ def font_exists?
+ require "win32ole" if RUBY_PLATFORM.match?(/mswin|mingw32|windows/)
+ fonts_dir = WIN32OLE.new("WScript.Shell").SpecialFolders("Fonts")
+ fonts_dir_local = Chef::Util::PathHelper.join(ENV["home"], "AppData/Local/Microsoft/Windows/fonts")
+ logger.trace("Seeing if the font at #{Chef::Util::PathHelper.join(fonts_dir, new_resource.font_name)} exists")
+ ::File.exist?(Chef::Util::PathHelper.join(fonts_dir, new_resource.font_name)) || ::File.exist?(Chef::Util::PathHelper.join(fonts_dir_local, new_resource.font_name))
+ end
+
+ # Parse out the schema provided to us to see if it's one we support via remote_file.
+ # We do this because URI will parse C:/foo as schema 'c', which won't work with remote_file
+ #
+ # @return [Boolean]
+ def remote_file_schema?(schema)
+ return true if %w{http https ftp}.include?(schema)
+ end
+
+ # return new_resource.source if we have a proper URI specified
+ # if it's a local file listed as a source return it in file:// format
+ #
+ # @return [String] path to the font
+ def source_uri
+ begin
+ require "uri" unless defined?(URI)
+ if remote_file_schema?(URI.parse(new_resource.source).scheme)
+ logger.trace("source property starts with ftp/http. Using source property unmodified")
+ return new_resource.source
+ end
+ rescue URI::InvalidURIError
+ Chef::Log.warn("source property of #{new_resource.source} could not be processed as a URI. Check the format you provided.")
+ end
+ logger.trace("source property does not start with ftp/http. Prepending with file:// as it appears to be a local file.")
+ "file://#{new_resource.source}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_package.rb b/lib/chef/resource/windows_package.rb
index 0e8dd39672..0072d70656 100644
--- a/lib/chef/resource/windows_package.rb
+++ b/lib/chef/resource/windows_package.rb
@@ -1,6 +1,6 @@
#
# Author:: Bryan McLellan <btm@loftninjas.org>
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,20 +16,107 @@
# limitations under the License.
#
-require "chef/mixin/uris"
-require "chef/resource/package"
-require "chef/provider/package/windows"
-require "chef/win32/error" if RUBY_PLATFORM =~ /mswin|mingw|windows/
+require_relative "../mixin/uris"
+require_relative "package"
+require_relative "../provider/package/windows"
+require_relative "../win32/error" if RUBY_PLATFORM.match?(/mswin|mingw|windows/)
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Resource
class WindowsPackage < Chef::Resource::Package
include Chef::Mixin::Uris
+ unified_mode true
- resource_name :windows_package
- provides :windows_package, os: "windows"
+ provides(:windows_package) { true }
provides :package, os: "windows"
+ description <<~DESC
+ Use the **windows_package** resource to manage packages on the Microsoft Windows platform.
+ The **windows_package** resource supports these installer formats:
+ * Microsoft Installer Package (MSI)
+ * Nullsoft Scriptable Install System (NSIS)
+ * Inno Setup (inno)
+ * Wise
+ * InstallShield
+ * Custom installers such as installing a non-.msi file that embeds an .msi-based installer
+
+ To enable idempotence of the `:install` action or to enable the `:remove` action with no source property specified,
+ `package_name` MUST be an exact match of the name used by the package installer. The names of installed packages
+ Windows knows about can be found in **Add/Remove programs**, in the output of `ohai packages`, or in the
+ `DisplayName` property in one of the following in the Windows registry:
+
+ * `HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall`
+ * `HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall`
+ * `HKEY_LOCAL_MACHINE\\Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall`
+
+ Note: If there are multiple versions of a package installed with the same display name, all of those packages will
+ be removed unless a version is provided in the **version** property or unless it can be discovered in the installer
+ file specified by the **source** property.
+ DESC
+
+ introduced "11.12"
+ examples <<~DOC
+ **Install a package**:
+
+ ```ruby
+ windows_package '7zip' do
+ action :install
+ source 'C:\\7z920.msi'
+ end
+ ```
+
+ **Specify a URL for the source attribute**:
+
+ ```ruby
+ windows_package '7zip' do
+ source 'http://www.7-zip.org/a/7z938-x64.msi'
+ end
+ ```
+
+ **Specify path and checksum**:
+
+ ```ruby
+ windows_package '7zip' do
+ source 'http://www.7-zip.org/a/7z938-x64.msi'
+ checksum '7c8e873991c82ad9cfc123415254ea6101e9a645e12977dcd518979e50fdedf3'
+ end
+ ```
+
+ **Modify remote_file resource attributes**:
+
+ The windows_package resource may specify a package at a remote location using the remote_file_attributes property. This uses the remote_file resource to download the contents at the specified URL and passes in a Hash that modifies the properties of the remote_file resource.
+
+ ```ruby
+ windows_package '7zip' do
+ source 'http://www.7-zip.org/a/7z938-x64.msi'
+ remote_file_attributes ({
+ :path => 'C:\\7zip.msi',
+ :checksum => '7c8e873991c82ad9cfc123415254ea6101e9a645e12977dcd518979e50fdedf3'
+ })
+ end
+ ```
+
+ **Download a nsis (Nullsoft) package resource**:
+
+ ```ruby
+ windows_package 'Mercurial 3.6.1 (64-bit)' do
+ source 'http://mercurial.selenic.com/release/windows/Mercurial-3.6.1-x64.exe'
+ checksum 'febd29578cb6736163d232708b834a2ddd119aa40abc536b2c313fc5e1b5831d'
+ end
+ ```
+
+ **Download a custom package**:
+
+ ```ruby
+ windows_package 'Microsoft Visual C++ 2005 Redistributable' do
+ source 'https://download.microsoft.com/download/6/B/B/6BB661D6-A8AE-4819-B79F-236472F6070C/vcredist_x86.exe'
+ installer_type :custom
+ options '/Q'
+ end
+ ```
+ DOC
+
allowed_actions :install, :remove
def initialize(name, run_context = nil)
@@ -37,19 +124,50 @@ class Chef
@source ||= source(@package_name) if @package_name.downcase.end_with?(".msi")
end
+ property :package_name, String,
+ description: "An optional property to set the package name if it differs from the resource block's name.",
+ identity: true
+
+ # we don't redefine the version property as a string here since we store the current value
+ # of version and that may be an array if multiple versions of a package are present on the system
+
+ # windows can't take array options yet
+ property :options, String,
+ description: "One (or more) additional options that are passed to the command."
+
# Unique to this resource
- property :installer_type, Symbol
- property :timeout, [ String, Integer ], default: 600
+ property :installer_type, Symbol,
+ equal_to: %i{custom inno installshield msi nsis wise},
+ description: "A symbol that specifies the type of package. Possible values: :custom (such as installing a non-.msi file that embeds an .msi-based installer), :inno (Inno Setup), :installshield (InstallShield), :msi (Microsoft Installer Package (MSI)), :nsis (Nullsoft Scriptable Install System (NSIS)), :wise (Wise)."
+
+ property :timeout, [ String, Integer ], default: 600,
+ default_description: "600 (seconds)",
+ description: "The amount of time (in seconds) to wait before timing out.",
+ desired_state: false
+
# 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
+ # we accept 3010 which means success, but a reboot is necessary
+ property :returns, [ String, Integer, Array ], default: [ 0, 3010 ],
+ desired_state: false,
+ description: "A comma-delimited list of return codes that indicate the success or failure of the package command that was run.",
+ default_description: "0 (success) and 3010 (success where a reboot is necessary)"
+
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
+ coerce: (proc do |s|
+ unless s.nil?
+ uri_scheme?(s) ? s : Chef::Util::PathHelper.canonical_path(s, false)
+ end
+ end),
+ default_description: "The resource block's name", # this property is basically a name_property but not really so we need to spell it out
+ description: "The path to a package in the local file system or the URL of a remote file that will be downloaded."
+
+ property :checksum, String,
+ desired_state: false, coerce: (proc { |c| c.downcase }),
+ description: "The SHA-256 checksum of the file. Use to prevent a file from being re-downloaded. When the local file matches the checksum, #{ChefUtils::Dist::Infra::PRODUCT} does not download it. Use when a URL is specified by the `source` property."
+
+ property :remote_file_attributes, Hash,
+ desired_state: false,
+ description: "If the source package to install is at a remote location, this property allows you to define a hash of properties which will be used by the underlying **remote_file** resource used to fetch the source."
end
end
end
diff --git a/lib/chef/resource/windows_pagefile.rb b/lib/chef/resource/windows_pagefile.rb
new file mode 100644
index 0000000000..4dfaae3be3
--- /dev/null
+++ b/lib/chef/resource/windows_pagefile.rb
@@ -0,0 +1,238 @@
+#
+# Copyright:: 2012-2018, Nordstrom, Inc.
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class WindowsPagefile < Chef::Resource
+ unified_mode true
+
+ provides(:windows_pagefile) { true }
+
+ description "Use the **windows_pagefile** resource to configure pagefile settings on Windows."
+ introduced "14.0"
+ examples <<~DOC
+ **Set the system to manage pagefiles**:
+
+ ```ruby
+ windows_pagefile 'Enable automatic management of pagefiles' do
+ automatic_managed true
+ end
+ ```
+
+ **Delete a pagefile**:
+
+ ```ruby
+ windows_pagefile 'Delete the pagefile' do
+ path 'C:\pagefile.sys'
+ action :delete
+ end
+ ```
+
+ **Create a pagefile with an initial and maximum size**:
+
+ ```ruby
+ windows_pagefile 'create the pagefile' do
+ path 'C:\pagefile.sys'
+ initial_size 100
+ maximum_size 200
+ end
+ ```
+ DOC
+
+ property :path, String,
+ coerce: proc { |x| x.tr("/", '\\') },
+ description: "An optional property to set the pagefile name if it differs from the resource block's name.",
+ name_property: true
+
+ property :system_managed, [TrueClass, FalseClass],
+ description: "Configures whether the system manages the pagefile size."
+
+ property :automatic_managed, [TrueClass, FalseClass],
+ description: "Enable automatic management of pagefile initial and maximum size. Setting this to true ignores `initial_size` and `maximum_size` properties.",
+ default: false
+
+ property :initial_size, Integer,
+ description: "Initial size of the pagefile in megabytes."
+
+ property :maximum_size, Integer,
+ description: "Maximum size of the pagefile in megabytes."
+
+ action :set do
+ description "Configures the default pagefile, creating if it doesn't exist."
+
+ pagefile = new_resource.path
+ initial_size = new_resource.initial_size
+ maximum_size = new_resource.maximum_size
+ system_managed = new_resource.system_managed
+ automatic_managed = new_resource.automatic_managed
+
+ if automatic_managed
+ set_automatic_managed unless automatic_managed?
+ else
+ unset_automatic_managed if automatic_managed?
+
+ # Check that the resource is not just trying to unset automatic managed, if it is do nothing more
+ if (initial_size && maximum_size) || system_managed
+ validate_name
+ create(pagefile) unless exists?(pagefile)
+
+ if system_managed
+ set_system_managed(pagefile) unless max_and_min_set?(pagefile, 0, 0)
+ else
+ unless max_and_min_set?(pagefile, initial_size, maximum_size)
+ set_custom_size(pagefile, initial_size, maximum_size)
+ end
+ end
+ end
+ end
+ end
+
+ action :delete do
+ description "Deletes the specified pagefile."
+
+ validate_name
+ delete(new_resource.path) if exists?(new_resource.path)
+ end
+
+ action_class do
+ private
+
+ # make sure the provided name property matches the appropriate format
+ # we do this here and not in the property itself because if automatic_managed
+ # is set then this validation is not necessary / doesn't make sense at all
+ def validate_name
+ return if /^.:.*.sys/.match?(new_resource.path)
+
+ raise "#{new_resource.path} does not match the format DRIVE:\\path\\file.sys for pagefiles. Example: C:\\pagefile.sys"
+ end
+
+ # See if the pagefile exists
+ #
+ # @param [String] pagefile path to the pagefile
+ # @return [Boolean]
+ def exists?(pagefile)
+ @exists ||= begin
+ logger.trace("Checking if #{pagefile} exists by running: wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" list /format:list")
+ cmd = shell_out("wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" list /format:list", returns: [0])
+ cmd.stderr.empty? && (cmd.stdout =~ /SettingID=#{get_setting_id(pagefile)}/i)
+ end
+ end
+
+ # is the max/min pagefile size set?
+ #
+ # @param [String] pagefile path to the pagefile
+ # @param [String] min the minimum size of the pagefile
+ # @param [String] max the minimum size of the pagefile
+ # @return [Boolean]
+ def max_and_min_set?(pagefile, min, max)
+ @max_and_min_set ||= begin
+ logger.trace("Checking if #{pagefile} min: #{min} and max #{max} are set")
+ cmd = shell_out("wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" list /format:list", returns: [0])
+ cmd.stderr.empty? && (cmd.stdout =~ /InitialSize=#{min}/i) && (cmd.stdout =~ /MaximumSize=#{max}/i)
+ end
+ end
+
+ # create a pagefile
+ #
+ # @param [String] pagefile path to the pagefile
+ def create(pagefile)
+ converge_by("create pagefile #{pagefile}") do
+ logger.trace("Running wmic.exe pagefileset create name=\"#{pagefile}\"")
+ cmd = shell_out("wmic.exe pagefileset create name=\"#{pagefile}\"")
+ check_for_errors(cmd.stderr)
+ end
+ end
+
+ # delete a pagefile
+ #
+ # @param [String] pagefile path to the pagefile
+ def delete(pagefile)
+ converge_by("remove pagefile #{pagefile}") do
+ logger.trace("Running wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" delete")
+ cmd = shell_out("wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" delete")
+ check_for_errors(cmd.stderr)
+ end
+ end
+
+ # see if the pagefile is automatically managed by Windows
+ #
+ # @return [Boolean]
+ def automatic_managed?
+ @automatic_managed ||= begin
+ logger.trace("Checking if pagefiles are automatically managed")
+ cmd = shell_out("wmic.exe computersystem where name=\"%computername%\" get AutomaticManagedPagefile /format:list")
+ cmd.stderr.empty? && (cmd.stdout =~ /AutomaticManagedPagefile=TRUE/i)
+ end
+ end
+
+ # turn on automatic management of all pagefiles by Windows
+ def set_automatic_managed
+ converge_by("set pagefile to Automatic Managed") do
+ logger.trace("Running wmic.exe computersystem where name=\"%computername%\" set AutomaticManagedPagefile=True")
+ cmd = shell_out("wmic.exe computersystem where name=\"%computername%\" set AutomaticManagedPagefile=True")
+ check_for_errors(cmd.stderr)
+ end
+ end
+
+ # turn off automatic management of all pagefiles by Windows
+ def unset_automatic_managed
+ converge_by("set pagefile to User Managed") do
+ logger.trace("Running wmic.exe computersystem where name=\"%computername%\" set AutomaticManagedPagefile=False")
+ cmd = shell_out("wmic.exe computersystem where name=\"%computername%\" set AutomaticManagedPagefile=False")
+ check_for_errors(cmd.stderr)
+ end
+ end
+
+ # set a custom size for the pagefile (vs the defaults)
+ #
+ # @param [String] pagefile path to the pagefile
+ # @param [String] min the minimum size of the pagefile
+ # @param [String] max the minimum size of the pagefile
+ def set_custom_size(pagefile, min, max)
+ converge_by("set #{pagefile} to InitialSize=#{min} & MaximumSize=#{max}") do
+ logger.trace("Running wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=#{min},MaximumSize=#{max}")
+ cmd = shell_out("wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=#{min},MaximumSize=#{max}", returns: [0])
+ check_for_errors(cmd.stderr)
+ end
+ end
+
+ # set a pagefile size to be system managed
+ #
+ # @param [String] pagefile path to the pagefile
+ def set_system_managed(pagefile)
+ converge_by("set #{pagefile} to System Managed") do
+ logger.trace("Running wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=0,MaximumSize=0")
+ cmd = shell_out("wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=0,MaximumSize=0", returns: [0])
+ check_for_errors(cmd.stderr)
+ end
+ end
+
+ def get_setting_id(pagefile)
+ split_path = pagefile.split('\\')
+ "#{split_path[1]} @ #{split_path[0]}"
+ end
+
+ # raise if there's an error on stderr on a shellout
+ def check_for_errors(stderr)
+ raise stderr.chomp unless stderr.empty?
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_path.rb b/lib/chef/resource/windows_path.rb
new file mode 100644
index 0000000000..870ffdef3f
--- /dev/null
+++ b/lib/chef/resource/windows_path.rb
@@ -0,0 +1,92 @@
+#
+# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>)
+# Copyright:: Copyright (c) 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_relative "../mixin/windows_env_helper" if ChefUtils.windows?
+require_relative "../mixin/wide_string"
+require_relative "../resource"
+
+class Chef
+ class Resource
+ class WindowsPath < Chef::Resource
+ unified_mode true
+
+ provides(:windows_path) { true }
+
+ description "Use the **windows_path** resource to manage the path environment variable on Microsoft Windows."
+ introduced "13.4"
+ examples <<~DOC
+ **Add Sysinternals to the system path**:
+
+ ```ruby
+ windows_path 'C:\\Sysinternals' do
+ action :add
+ end
+ ```
+
+ **Remove 7-Zip from the system path**:
+
+ ```ruby
+ windows_path 'C:\\7-Zip' do
+ action :remove
+ end
+ ```
+ DOC
+
+ allowed_actions :add, :remove
+ default_action :add
+
+ property :path, String,
+ description: "An optional property to set the path value if it differs from the resource block's name.",
+ name_property: true
+
+ action_class do
+ include Chef::Mixin::WindowsEnvHelper if ChefUtils.windows?
+
+ def load_current_resource
+ @current_resource = Chef::Resource::WindowsPath.new(new_resource.name)
+ @current_resource.path(new_resource.path)
+ @current_resource
+ end
+ end
+
+ action :add do
+ # The windows Env provider does not correctly expand variables in
+ # the PATH environment variable. Ruby expects these to be expanded.
+ #
+ path = expand_path(new_resource.path)
+ env "path" do
+ action :modify
+ delim ::File::PATH_SEPARATOR
+ value path.tr("/", '\\')
+ end
+ end
+
+ action :remove do
+ # The windows Env provider does not correctly expand variables in
+ # the PATH environment variable. Ruby expects these to be expanded.
+ #
+ path = expand_path(new_resource.path)
+ env "path" do
+ action :delete
+ delim ::File::PATH_SEPARATOR
+ value path.tr("/", '\\')
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_printer.rb b/lib/chef/resource/windows_printer.rb
new file mode 100644
index 0000000000..dea15ba112
--- /dev/null
+++ b/lib/chef/resource/windows_printer.rb
@@ -0,0 +1,166 @@
+#
+# Author:: Doug Ireton (<doug@1strategy.com>)
+# Copyright:: 2012-2018, Nordstrom, 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.
+#
+# See here for more info:
+# http://msdn.microsoft.com/en-us/library/windows/desktop/aa394492(v=vs.85).aspx
+
+require_relative "../resource"
+
+class Chef
+ class Resource
+ class WindowsPrinter < Chef::Resource
+ unified_mode true
+
+ autoload :Resolv, "resolv"
+
+ provides(:windows_printer) { true }
+
+ description "Use the **windows_printer** resource to setup Windows printers. Note that this doesn't currently install a printer driver. You must already have the driver installed on the system."
+ introduced "14.0"
+ examples <<~DOC
+ **Create a printer**:
+
+ ```ruby
+ windows_printer 'HP LaserJet 5th Floor' do
+ driver_name 'HP LaserJet 4100 Series PCL6'
+ ipv4_address '10.4.64.38'
+ end
+ ```
+
+ **Delete a printer**:
+
+ Note: this doesn't delete the associated printer port. See windows_printer_port above for how to delete the port.
+
+ ```ruby
+ windows_printer 'HP LaserJet 5th Floor' do
+ action :delete
+ end
+ ```
+ DOC
+
+ property :device_id, String,
+ description: "An optional property to set the printer queue name if it differs from the resource block's name. Example: `HP LJ 5200 in fifth floor copy room`.",
+ name_property: true
+
+ property :comment, String,
+ description: "Optional descriptor for the printer queue."
+
+ property :default, [TrueClass, FalseClass],
+ description: "Determines whether or not this should be the system's default printer.",
+ default: false
+
+ property :driver_name, String,
+ description: "The exact name of printer driver installed on the system.",
+ required: [:create]
+
+ property :location, String,
+ description: "Printer location, such as `Fifth floor copy room`."
+
+ property :shared, [TrueClass, FalseClass],
+ description: "Determines whether or not the printer is shared.",
+ default: false
+
+ property :share_name, String,
+ description: "The name used to identify the shared printer."
+
+ property :ipv4_address, String,
+ description: "The IPv4 address of the printer, such as `10.4.64.23`",
+ callbacks: {
+ "The ipv4_address property must be in the IPv4 format of `WWW.XXX.YYY.ZZZ`" =>
+ proc { |v| v.match(Resolv::IPv4::Regex) },
+ }
+
+ PRINTERS_REG_KEY = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Printers\\'.freeze unless defined?(PRINTERS_REG_KEY)
+
+ # @todo Set @current_resource printer properties from registry
+ load_current_value do |desired|
+ name desired.name
+ end
+
+ action :create do
+ description "Create a new printer and a printer port if one doesn't already exist."
+
+ if printer_exists?
+ Chef::Log.info "#{@new_resource} already exists - nothing to do."
+ else
+ converge_by("Create #{@new_resource}") do
+ create_printer
+ end
+ end
+ end
+
+ action :delete do
+ description "Delete an existing printer. Note this does not delete the associated printer port."
+
+ if printer_exists?
+ converge_by("Delete #{@new_resource}") do
+ delete_printer
+ end
+ else
+ Chef::Log.info "#{@current_resource} doesn't exist - can't delete."
+ end
+ end
+
+ action_class do
+ private
+
+ # does the printer exist
+ #
+ # @param [String] name the name of the printer
+ # @return [Boolean]
+ def printer_exists?
+ printer_reg_key = PRINTERS_REG_KEY + new_resource.name
+ logger.trace "Checking to see if this reg key exists: '#{printer_reg_key}'"
+ registry_key_exists?(printer_reg_key)
+ end
+
+ # creates the printer port and then the printer
+ def create_printer
+ # Create the printer port first
+ windows_printer_port new_resource.ipv4_address
+
+ port_name = "IP_#{new_resource.ipv4_address}"
+
+ declare_resource(:powershell_script, "Creating printer: #{new_resource.device_id}") do
+ code <<-EOH
+
+ Set-WmiInstance -class Win32_Printer `
+ -EnableAllPrivileges `
+ -Argument @{ DeviceID = "#{new_resource.device_id}";
+ Comment = "#{new_resource.comment}";
+ Default = "$#{new_resource.default}";
+ DriverName = "#{new_resource.driver_name}";
+ Location = "#{new_resource.location}";
+ PortName = "#{port_name}";
+ Shared = "$#{new_resource.shared}";
+ ShareName = "#{new_resource.share_name}";
+ }
+ EOH
+ end
+ end
+
+ def delete_printer
+ declare_resource(:powershell_script, "Deleting printer: #{new_resource.device_id}") do
+ code <<-EOH
+ $printer = Get-WMIObject -class Win32_Printer -EnableAllPrivileges -Filter "name = '#{new_resource.device_id}'"
+ $printer.Delete()
+ EOH
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_printer_port.rb b/lib/chef/resource/windows_printer_port.rb
new file mode 100644
index 0000000000..2a4eaa09b3
--- /dev/null
+++ b/lib/chef/resource/windows_printer_port.rb
@@ -0,0 +1,166 @@
+#
+# Author:: Doug Ireton <doug@1strategy.com>
+# Copyright:: 2012-2018, Nordstrom, 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.
+#
+# See here for more info:
+# http://msdn.microsoft.com/en-us/library/windows/desktop/aa394492(v=vs.85).aspx
+
+require_relative "../resource"
+
+class Chef
+ class Resource
+ class WindowsPrinterPort < Chef::Resource
+ unified_mode true
+
+ autoload :Resolv, "resolv"
+
+ provides(:windows_printer_port) { true }
+
+ description "Use the **windows_printer_port** resource to create and delete TCP/IPv4 printer ports on Windows."
+ introduced "14.0"
+ examples <<~DOC
+ **Delete a printer port**
+
+ ```ruby
+ windows_printer_port '10.4.64.37' do
+ action :delete
+ end
+ ```
+
+ **Delete a port with a custom port_name**
+
+ ```ruby
+ windows_printer_port '10.4.64.38' do
+ port_name 'My awesome port'
+ action :delete
+ end
+ ```
+
+ **Create a port with more options**
+
+ ```ruby
+ windows_printer_port '10.4.64.39' do
+ port_name 'My awesome port'
+ snmp_enabled true
+ port_protocol 2
+ end
+ ```
+ DOC
+
+ property :ipv4_address, String,
+ name_property: true,
+ description: "An optional property for the IPv4 address of the printer if it differs from the resource block's name.",
+ callbacks: {
+ "The ipv4_address property must be in the format of WWW.XXX.YYY.ZZZ!" =>
+ proc { |v| v.match(Resolv::IPv4::Regex) },
+ }
+
+ property :port_name, String,
+ description: "The port name."
+
+ property :port_number, Integer,
+ description: "The port number.",
+ default: 9100
+
+ property :port_description, String,
+ description: "The description of the port."
+
+ property :snmp_enabled, [TrueClass, FalseClass],
+ description: "Determines if SNMP is enabled on the port.",
+ default: false
+
+ property :port_protocol, Integer,
+ description: "The printer port protocol: 1 (RAW) or 2 (LPR).",
+ validation_message: "port_protocol must be either 1 for RAW or 2 for LPR!",
+ default: 1, equal_to: [1, 2]
+
+ PORTS_REG_KEY = 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Monitors\Standard TCP/IP Port\Ports\\'.freeze unless defined?(PORTS_REG_KEY)
+
+ # @todo Set @current_resource port properties from registry
+ load_current_value do |desired|
+ name desired.name
+ ipv4_address desired.ipv4_address
+ port_name desired.port_name || "IP_#{desired.ipv4_address}"
+ end
+
+ action :create do
+ description "Create the new printer port if it does not already exist."
+
+ if port_exists?
+ Chef::Log.info "#{@new_resource} already exists - nothing to do."
+ else
+ converge_by("Create #{@new_resource}") do
+ create_printer_port
+ end
+ end
+ end
+
+ action :delete do
+ description "Delete an existing printer port."
+
+ if port_exists?
+ converge_by("Delete #{@new_resource}") do
+ delete_printer_port
+ end
+ else
+ Chef::Log.info "#{@current_resource} doesn't exist - can't delete."
+ end
+ end
+
+ action_class do
+ private
+
+ def port_exists?
+ name = new_resource.port_name || "IP_#{new_resource.ipv4_address}"
+ port_reg_key = PORTS_REG_KEY + name
+
+ logger.trace "Checking to see if this reg key exists: '#{port_reg_key}'"
+ registry_key_exists?(port_reg_key)
+ end
+
+ def create_printer_port
+ port_name = new_resource.port_name || "IP_#{new_resource.ipv4_address}"
+
+ # create the printer port using PowerShell
+ declare_resource(:powershell_script, "Creating printer port #{new_resource.port_name}") do
+ code <<-EOH
+
+ Set-WmiInstance -class Win32_TCPIPPrinterPort `
+ -EnableAllPrivileges `
+ -Argument @{ HostAddress = "#{new_resource.ipv4_address}";
+ Name = "#{port_name}";
+ Description = "#{new_resource.port_description}";
+ PortNumber = "#{new_resource.port_number}";
+ Protocol = "#{new_resource.port_protocol}";
+ SNMPEnabled = "$#{new_resource.snmp_enabled}";
+ }
+ EOH
+ end
+ end
+
+ def delete_printer_port
+ port_name = new_resource.port_name || "IP_#{new_resource.ipv4_address}"
+
+ declare_resource(:powershell_script, "Deleting printer port: #{new_resource.port_name}") do
+ code <<-EOH
+ $port = Get-WMIObject -class Win32_TCPIPPrinterPort -EnableAllPrivileges -Filter "name = '#{port_name}'"
+ $port.Delete()
+ EOH
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_script.rb b/lib/chef/resource/windows_script.rb
index 7c39d9fba0..8c958c5cba 100644
--- a/lib/chef/resource/windows_script.rb
+++ b/lib/chef/resource/windows_script.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Edwards (<adamed@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,46 +16,33 @@
# limitations under the License.
#
-require "chef/platform/query_helpers"
-require "chef/resource/script"
-require "chef/mixin/windows_architecture_helper"
+require_relative "script"
+require_relative "../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)
+ include Chef::Mixin::WindowsArchitectureHelper
- protected
+ unified_mode true
- def initialize(name, run_context, resource_name, interpreter_command)
- super(name, run_context)
- @interpreter = interpreter_command
- @resource_name = resource_name if resource_name
- @default_guard_interpreter = self.resource_name
- end
-
- include Chef::Mixin::WindowsArchitectureHelper
+ # This is an abstract resource meant to be subclasses; thus no 'provides'
- public
+ set_guard_inherited_attributes(:architecture)
def architecture(arg = nil)
- assert_architecture_compatible!(arg) if ! arg.nil?
+ assert_architecture_compatible!(arg) unless arg.nil?
result = set_or_return(
:architecture,
arg,
- :kind_of => Symbol
+ kind_of: Symbol
)
end
protected
def assert_architecture_compatible!(desired_architecture)
- if desired_architecture == :i386 && Chef::Platform.windows_nano_server?
- raise Chef::Exceptions::Win32ArchitectureIncorrect,
- "cannot execute script with requested architecture 'i386' on Windows Nano Server"
- elsif ! node_supports_windows_architecture?(node, desired_architecture)
+ unless 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
diff --git a/lib/chef/resource/windows_security_policy.rb b/lib/chef/resource/windows_security_policy.rb
new file mode 100644
index 0000000000..78d56e2e46
--- /dev/null
+++ b/lib/chef/resource/windows_security_policy.rb
@@ -0,0 +1,165 @@
+#
+# Author:: Ashwini Nehate (<anehate@chef.io>)
+# Author:: Davin Taddeo (<davin@chef.io>)
+# Author:: Jeff Brimager (<jbrimager@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../resource"
+require "tempfile" unless defined?(Tempfile)
+
+class Chef
+ class Resource
+ class WindowsSecurityPolicy < Chef::Resource
+ unified_mode true
+
+ provides :windows_security_policy
+
+ # The valid policy_names options found here
+ # https://github.com/ChrisAWalker/cSecurityOptions under 'AccountSettings'
+ policy_names = %w{LockoutDuration
+ MaximumPasswordAge
+ MinimumPasswordAge
+ MinimumPasswordLength
+ PasswordComplexity
+ PasswordHistorySize
+ LockoutBadCount
+ ResetLockoutCount
+ RequireLogonToChangePassword
+ ForceLogoffWhenHourExpire
+ NewAdministratorName
+ NewGuestName
+ ClearTextPassword
+ LSAAnonymousNameLookup
+ EnableAdminAccount
+ EnableGuestAccount
+ }
+ description "Use the **windows_security_policy** resource to set a security policy on the Microsoft Windows platform."
+ introduced "16.0"
+
+ examples <<~DOC
+ **Set Administrator Account to Enabled**:
+
+ ```ruby
+ windows_security_policy 'EnableAdminAccount' do
+ secvalue '1'
+ action :set
+ end
+ ```
+
+ **Rename Administrator Account**:
+
+ ```ruby
+ windows_security_policy 'NewAdministratorName' do
+ secvalue 'AwesomeChefGuy'
+ action :set
+ end
+ ```
+
+ **Set Guest Account to Disabled**:
+
+ ```ruby
+ windows_security_policy 'EnableGuestAccount' do
+ secvalue '0'
+ action :set
+ end
+ ```
+ DOC
+
+ property :secoption, String, name_property: true, required: true, equal_to: policy_names,
+ description: "The name of the policy to be set on windows platform to maintain its security."
+
+ property :secvalue, String, required: true,
+ description: "Policy value to be set for policy name."
+
+ load_current_value do |desired|
+ current_state = load_security_options
+
+ if desired.secoption == "ResetLockoutCount"
+ if desired.secvalue.to_i > 30
+ raise Chef::Exceptions::ValidationFailed, "The \"ResetLockoutCount\" value cannot be greater than 30 minutes"
+ end
+ end
+ if (desired.secoption == "ResetLockoutCount" || desired.secoption == "LockoutDuration") && current_state["LockoutBadCount"] == "0"
+ raise Chef::Exceptions::ValidationFailed, "#{desired.secoption} cannot be set unless the \"LockoutBadCount\" security policy has been set to a non-zero value"
+ end
+
+ secvalue current_state[desired.secoption.to_s]
+ end
+
+ action :set do
+ converge_if_changed :secvalue do
+ security_option = new_resource.secoption
+ security_value = new_resource.secvalue
+
+ file = Tempfile.new(["#{security_option}", ".inf"])
+ case security_option
+ when "LockoutBadCount"
+ cmd = "net accounts /LockoutThreshold:#{security_value}"
+ when "ResetLockoutCount"
+ cmd = "net accounts /LockoutWindow:#{security_value}"
+ when "LockoutDuration"
+ cmd = "net accounts /LockoutDuration:#{security_value}"
+ when "NewAdministratorName", "NewGuestName"
+ policy_line = "#{security_option} = \"#{security_value}\""
+ file.write("[Unicode]\r\nUnicode=yes\r\n[System Access]\r\n#{policy_line}\r\n[Version]\r\nsignature=\"$CHICAGO$\"\r\nRevision=1\r\n")
+ file.close
+ file_path = file.path.gsub("/", '\\')
+ cmd = "C:\\Windows\\System32\\secedit /configure /db C:\\windows\\security\\new.sdb /cfg #{file_path} /areas SECURITYPOLICY"
+ else
+ policy_line = "#{security_option} = #{security_value}"
+ file.write("[Unicode]\r\nUnicode=yes\r\n[System Access]\r\n#{policy_line}\r\n[Version]\r\nsignature=\"$CHICAGO$\"\r\nRevision=1\r\n")
+ file.close
+ file_path = file.path.gsub("/", '\\')
+ cmd = "C:\\Windows\\System32\\secedit /configure /db C:\\windows\\security\\new.sdb /cfg #{file_path} /areas SECURITYPOLICY"
+ end
+ shell_out!(cmd)
+ file.unlink
+ end
+ end
+
+ private
+
+ # Loads powershell to get current state on security options
+ def load_security_options
+ powershell_code = <<-CODE
+ C:\\Windows\\System32\\secedit /export /cfg $env:TEMP\\secopts_export.inf | Out-Null
+ # cspell:disable-next-line
+ $security_options_data = (Get-Content $env:TEMP\\secopts_export.inf | Select-String -Pattern "^[CEFLMNPR].* =.*$" | Out-String)
+ Remove-Item $env:TEMP\\secopts_export.inf -force
+ $security_options_hash = ($security_options_data -Replace '"'| ConvertFrom-StringData)
+ ([PSCustomObject]@{
+ RequireLogonToChangePassword = $security_options_hash.RequireLogonToChangePassword
+ PasswordComplexity = $security_options_hash.PasswordComplexity
+ LSAAnonymousNameLookup = $security_options_hash.LSAAnonymousNameLookup
+ EnableAdminAccount = $security_options_hash.EnableAdminAccount
+ PasswordHistorySize = $security_options_hash.PasswordHistorySize
+ MinimumPasswordLength = $security_options_hash.MinimumPasswordLength
+ ResetLockoutCount = $security_options_hash.ResetLockoutCount
+ MaximumPasswordAge = $security_options_hash.MaximumPasswordAge
+ ClearTextPassword = $security_options_hash.ClearTextPassword
+ NewAdministratorName = $security_options_hash.NewAdministratorName
+ LockoutDuration = $security_options_hash.LockoutDuration
+ EnableGuestAccount = $security_options_hash.EnableGuestAccount
+ ForceLogoffWhenHourExpire = $security_options_hash.ForceLogoffWhenHourExpire
+ MinimumPasswordAge = $security_options_hash.MinimumPasswordAge
+ NewGuestName = $security_options_hash.NewGuestName
+ LockoutBadCount = $security_options_hash.LockoutBadCount
+ })
+ CODE
+ powershell_exec(powershell_code).result
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_service.rb b/lib/chef/resource/windows_service.rb
index 405f7f6dbe..779c0e22ad 100644
--- a/lib/chef/resource/windows_service.rb
+++ b/lib/chef/resource/windows_service.rb
@@ -1,6 +1,6 @@
#
# Author:: Bryan McLellan <btm@loftninjas.org>
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,56 +16,222 @@
# limitations under the License.
#
-require "chef/resource/service"
+require_relative "service"
+require_relative "../win32_service_constants"
class Chef
class Resource
class WindowsService < Chef::Resource::Service
+ include Chef::Win32ServiceConstants
+ unified_mode true
+
+ ALLOWED_START_TYPES = {
+ automatic: SERVICE_AUTO_START,
+ manual: SERVICE_DEMAND_START,
+ disabled: SERVICE_DISABLED,
+ }.freeze
# Until #1773 is resolved, you need to manually specify the windows_service resource
- # to use action :configure_startup and attribute startup_type
+ # to use action :configure_startup and properties startup_type
- provides :windows_service, os: "windows"
+ provides(:windows_service) { true }
provides :service, os: "windows"
- allowed_actions :configure_startup
+ description "Use the **windows_service** resource to create, delete, or manage a service on the Microsoft Windows platform."
+ introduced "12.0"
+ examples <<~DOC
+ **Starting Services**
+
+ Start a service with a `manual` startup type:
+
+ ```ruby
+ windows_service 'BITS' do
+ action :configure_startup
+ startup_type :manual
+ end
+ ```
+
+ **Creating Services**
- identity_attr :service_name
+ Create a service named chef-client:
+
+ ```ruby
+ windows_service 'chef-client' do
+ action :create
+ binary_path_name "C:\\opscode\\chef\\bin"
+ end
+ ```
+
+ Create a service with `service_name` and `display_name`:
+
+ ```ruby
+ windows_service 'Setup chef-client as a service' do
+ action :create
+ display_name 'CHEF-CLIENT'
+ service_name 'chef-client'
+ binary_path_name "C:\\opscode\\chef\\bin"
+ end
+ ```
+
+ Create a service with the `manual` startup type:
+
+ ```ruby
+ windows_service 'chef-client' do
+ action :create
+ binary_path_name "C:\\opscode\\chef\\bin"
+ startup_type :manual
+ end
+ ```
+
+ Create a service with the `disabled` startup type:
+
+ ```ruby
+ windows_service 'chef-client' do
+ action :create
+ binary_path_name "C:\\opscode\\chef\\bin"
+ startup_type :disabled
+ end
+ ```
+
+ Create a service with the `automatic` startup type and delayed start enabled:
+
+ ```ruby
+ windows_service 'chef-client' do
+ action :create
+ binary_path_name "C:\\opscode\\chef\\bin"
+ startup_type :automatic
+ delayed_start true
+ end
+ ```
- state_attrs :enabled, :running
+ Create a service with a description:
- def initialize(name, run_context = nil)
- super
- @startup_type = :automatic
- @run_as_user = ""
- @run_as_password = ""
+ ```ruby
+ windows_service 'chef-client' do
+ action :create
+ binary_path_name "C:\\opscode\\chef\\bin"
+ startup_type :automatic
+ description "Chef client as service"
end
+ ```
- def startup_type(arg = nil)
- # Set-Service arguments are automatic and manual
- # Win32::Service returns 'auto start' or 'demand start' respectively, which the provider currently uses
- set_or_return(
- :startup_type,
- arg,
- :equal_to => [ :automatic, :manual, :disabled ]
- )
+ **Deleting Services**
+
+ Delete a service named chef-client:
+
+ ```ruby
+ windows_service 'chef-client' do
+ action :delete
end
+ ```
+
+ Delete a service with the `service_name` property:
- def run_as_user(arg = nil)
- set_or_return(
- :run_as_user,
- arg,
- :kind_of => [ String ]
- )
+ ```ruby
+ windows_service 'Delete chef client' do
+ action :delete
+ service_name 'chef-client'
end
+ ```
+
+ **Configuring Services**
+
+ Change an existing service from automatic to manual startup:
- def run_as_password(arg = nil)
- set_or_return(
- :run_as_password,
- arg,
- :kind_of => [ String ]
- )
+ ```ruby
+ windows_service 'chef-client' do
+ action :configure
+ binary_path_name "C:\\opscode\\chef\\bin"
+ startup_type :manual
end
+ ```
+ DOC
+
+ allowed_actions :configure_startup, :create, :delete, :configure
+
+ property :timeout, Integer,
+ description: "The amount of time (in seconds) to wait before timing out.",
+ default: 60,
+ desired_state: false
+
+ property :display_name, String, regex: /^.{1,256}$/,
+ description: "The display name to be used by user interface programs to identify the service. This string has a maximum length of 256 characters.",
+ validation_message: "The display_name can only be a maximum of 256 characters!",
+ introduced: "14.0"
+
+ # https://github.com/chef/win32-service/blob/ffi/lib/win32/windows/constants.rb#L19-L29
+ property :desired_access, Integer,
+ default: SERVICE_ALL_ACCESS,
+ introduced: "14.0"
+
+ # https://github.com/chef/win32-service/blob/ffi/lib/win32/windows/constants.rb#L31-L41
+ property :service_type, Integer, default: SERVICE_WIN32_OWN_PROCESS,
+ introduced: "14.0"
+
+ # Valid options:
+ # - :automatic
+ # - :manual
+ # - :disabled
+ # Reference: https://github.com/chef/win32-service/blob/ffi/lib/win32/windows/constants.rb#L49-L54
+ property :startup_type, [Symbol],
+ equal_to: %i{automatic manual disabled},
+ default: :automatic,
+ description: "Use to specify the startup type of the service.",
+ coerce: proc { |x|
+ if x.is_a?(Integer)
+ ALLOWED_START_TYPES.invert.fetch(x) do
+ Chef::Log.warn("Unsupported startup_type #{x}, falling back to :automatic")
+ :automatic
+ end
+ elsif x.is_a?(String)
+ x.to_sym
+ else
+ x
+ end
+ }
+
+ # 1 == delayed start is enabled
+ # 0 == NO delayed start
+ property :delayed_start, [TrueClass, FalseClass],
+ introduced: "14.0",
+ description: "Set the startup type to delayed start. This only applies if `startup_type` is `:automatic`",
+ default: false, coerce: proc { |x|
+ if x.is_a?(Integer)
+ x == 0 ? false : true
+ else
+ x
+ end
+ }
+
+ # https://github.com/chef/win32-service/blob/ffi/lib/win32/windows/constants.rb#L43-L47
+ property :error_control, Integer,
+ default: SERVICE_ERROR_NORMAL,
+ introduced: "14.0"
+
+ property :binary_path_name, String,
+ introduced: "14.0",
+ description: "The fully qualified path to the service binary file. The path can also include arguments for an auto-start service. This is required for `:create` and `:configure` actions"
+
+ property :load_order_group, String,
+ introduced: "14.0",
+ description: "The name of the service's load ordering group(s)."
+
+ property :dependencies, [String, Array],
+ description: "A pointer to a double null-terminated array of null-separated names of services or load ordering groups that the system must start before this service. Specify `nil` or an empty string if the service has no dependencies. Dependency on a group means that this service can run if at least one member of the group is running after an attempt to start all members of the group.",
+ introduced: "14.0"
+
+ property :description, String,
+ description: "Description of the service.",
+ introduced: "14.0"
+
+ property :run_as_user, String,
+ description: "The user under which a Microsoft Windows service runs.",
+ default: "localsystem",
+ coerce: proc { |x| x.downcase }
+
+ property :run_as_password, String,
+ description: "The password for the user specified by `run_as_user`.",
+ default: ""
end
end
end
diff --git a/lib/chef/resource/windows_share.rb b/lib/chef/resource/windows_share.rb
new file mode 100644
index 0000000000..fe1e976747
--- /dev/null
+++ b/lib/chef/resource/windows_share.rb
@@ -0,0 +1,345 @@
+#
+# Author:: Sölvi Páll Ásgeirsson (<solvip@gmail.com>)
+# Author:: Richard Lavey (richard.lavey@calastone.com)
+# Author:: Tim Smith (tsmith@chef.io)
+#
+# Copyright:: 2014-2017, Sölvi Páll Ásgeirsson.
+# Copyright:: Copyright (c) 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_relative "../resource"
+require_relative "../util/path_helper"
+
+class Chef
+ class Resource
+ class WindowsShare < Chef::Resource
+ unified_mode true
+
+ provides :windows_share
+
+ description "Use the **windows_share** resource to create, modify and remove Windows shares."
+ introduced "14.7"
+ examples <<~DOC
+ **Create a share**:
+
+ ```ruby
+ windows_share 'foo' do
+ action :create
+ path 'C:\\foo'
+ full_users ['DOMAIN_A\\some_user', 'DOMAIN_B\\some_other_user']
+ read_users ['DOMAIN_C\\Domain users']
+ end
+ ```
+
+ **Delete a share**:
+
+ ```ruby
+ windows_share 'foo' do
+ action :delete
+ end
+ ```
+ DOC
+
+ # Specifies a name for the SMB share. The name may be composed of any valid file name characters, but must be less than 80 characters long. The names pipe and mailslot are reserved for use by the computer.
+ property :share_name, String,
+ description: "An optional property to set the share name if it differs from the resource block's name.",
+ name_property: true
+
+ # Specifies the path of the location of the folder to share. The path must be fully qualified. Relative paths or paths that contain wildcard characters are not permitted.
+ property :path, String,
+ description: "The path of the folder to share. Required when creating. If the share already exists on a different path then it is deleted and re-created.",
+ coerce: proc { |p| p.tr("/", "\\") || p }
+
+ # Specifies an optional description of the SMB share. A description of the share is displayed by running the Get-SmbShare cmdlet. The description may not contain more than 256 characters.
+ property :description, String,
+ description: "The description to be applied to the share.",
+ default: ""
+
+ # Specifies which accounts are granted full permission to access the share. Use a comma-separated list to specify multiple accounts. An account may not be specified more than once in the FullAccess, ChangeAccess, or ReadAccess parameter lists, but may be specified once in the FullAccess, ChangeAccess, or ReadAccess parameter list and once in the NoAccess parameter list.
+ property :full_users, Array,
+ description: "The users that should have 'Full control' permissions on the share in domain\\username format.",
+ default: lazy { [] }, coerce: proc { |u| u.sort }
+
+ # Specifies which users are granted modify permission to access the share
+ property :change_users, Array,
+ description: "The users that should have 'modify' permission on the share in domain\\username format.",
+ default: lazy { [] }, coerce: proc { |u| u.sort }
+
+ # Specifies which users are granted read permission to access the share. Multiple users can be specified by supplying a comma-separated list.
+ property :read_users, Array,
+ description: "The users that should have 'read' permission on the share in domain\\username format.",
+ default: lazy { [] }, coerce: proc { |u| u.sort }
+
+ # Specifies the lifetime of the new SMB share. A temporary share does not persist beyond the next restart of the computer. By default, new SMB shares are persistent, and non-temporary.
+ property :temporary, [TrueClass, FalseClass],
+ description: "The lifetime of the new SMB share. A temporary share does not persist beyond the next restart of the computer.",
+ default: false
+
+ # Specifies the scope name of the share.
+ property :scope_name, String,
+ description: "The scope name of the share.",
+ default: "*"
+
+ # Specifies the continuous availability time-out for the share.
+ property :ca_timeout, Integer,
+ description: "The continuous availability time-out for the share.",
+ default: 0
+
+ # Indicates that the share is continuously available.
+ property :continuously_available, [TrueClass, FalseClass],
+ description: "Indicates that the share is continuously available.",
+ default: false
+
+ # Specifies the caching mode of the offline files for the SMB share.
+ # property :caching_mode, String, equal_to: %w(None Manual Documents Programs BranchCache)
+
+ # Specifies the maximum number of concurrently connected users that the new SMB share may accommodate. If this parameter is set to zero (0), then the number of users is unlimited.
+ property :concurrent_user_limit, Integer,
+ description: "The maximum number of concurrently connected users the share can accommodate.",
+ default: 0
+
+ # Indicates that the share is encrypted.
+ property :encrypt_data, [TrueClass, FalseClass],
+ description: "Indicates that the share is encrypted.",
+ default: false
+
+ # Specifies which files and folders in the SMB share are visible to users. AccessBased: SMB does not the display the files and folders for a share to a user unless that user has rights to access the files and folders. By default, access-based enumeration is disabled for new SMB shares. Unrestricted: SMB displays files and folders to a user even when the user does not have permission to access the items.
+ # property :folder_enumeration_mode, String, equal_to: %(AccessBased Unrestricted)
+
+ load_current_value do |desired|
+ # this command selects individual objects because EncryptData & CachingMode have underlying
+ # types that get converted to their Integer values by ConvertTo-Json & we need to make sure
+ # those get written out as strings
+ share_state_cmd = "Get-SmbShare -Name '#{desired.share_name}' | Select-Object Name,Path, Description, Temporary, CATimeout, ContinuouslyAvailable, ConcurrentUserLimit, EncryptData"
+
+ Chef::Log.debug("Running '#{share_state_cmd}' to determine share state'")
+ ps_results = powershell_exec(share_state_cmd)
+
+ # detect a failure without raising and then set current_resource to nil
+ if ps_results.error?
+ Chef::Log.debug("Error fetching share state: #{ps_results.errors}")
+ current_value_does_not_exist!
+ end
+
+ Chef::Log.debug("The Get-SmbShare results were #{ps_results.result}")
+ results = ps_results.result
+
+ path results["Path"]
+ description results["Description"]
+ temporary results["Temporary"]
+ ca_timeout results["CATimeout"]
+ continuously_available results["ContinuouslyAvailable"]
+ # caching_mode results['CachingMode']
+ concurrent_user_limit results["ConcurrentUserLimit"]
+ encrypt_data results["EncryptData"]
+ # folder_enumeration_mode results['FolderEnumerationMode']
+
+ perm_state_cmd = %{Get-SmbShareAccess -Name "#{desired.share_name}" | Select-Object AccountName,AccessControlType,AccessRight}
+
+ Chef::Log.debug("Running '#{perm_state_cmd}' to determine share permissions state'")
+ ps_perm_results = powershell_exec(perm_state_cmd)
+
+ # we raise here instead of warning like above because we'd only get here if the above Get-SmbShare
+ # command was successful and that continuing would leave us with 1/2 known state
+ raise "Could not determine #{desired.share_name} share permissions by running '#{perm_state_cmd}'" if ps_perm_results.error?
+
+ Chef::Log.debug("The Get-SmbShareAccess results were #{ps_perm_results.result}")
+
+ f_users, c_users, r_users = parse_permissions(ps_perm_results.result)
+
+ full_users f_users
+ change_users c_users
+ read_users r_users
+ end
+
+ # given the string output of Get-SmbShareAccess parse out
+ # arrays of full access users, change users, and read only users
+ def parse_permissions(json_results)
+ json_results = [json_results] unless json_results.is_a?(Array) # single result is not an array
+
+ f_users = []
+ c_users = []
+ r_users = []
+
+ json_results.each do |perm|
+ next unless perm["AccessControlType"] == 0 # allow
+
+ case perm["AccessRight"]
+ when 0 then f_users << stripped_account(perm["AccountName"]) # 0 full control
+ when 1 then c_users << stripped_account(perm["AccountName"]) # 1 == change
+ when 2 then r_users << stripped_account(perm["AccountName"]) # 2 == read
+ end
+ end
+ [f_users, c_users, r_users]
+ end
+
+ # local names are returned from Get-SmbShareAccess in the full format MACHINE\\NAME
+ # but users of this resource would simply say NAME so we need to strip the values for comparison
+ def stripped_account(name)
+ name.slice!("#{node["hostname"]}\\")
+ name
+ end
+
+ action :create do
+ description "Create and modify Windows shares."
+
+ # we do this here instead of requiring the property because :delete doesn't need path set
+ raise "No path property set" unless new_resource.path
+
+ converge_if_changed do
+ # you can't actually change the path so you have to delete the old share first
+ if different_path?
+ Chef::Log.debug("The path has changed so we will delete and recreate share")
+ delete_share
+ create_share
+ elsif current_resource.nil?
+ # powershell cmdlet for create is different than updates
+ Chef::Log.debug("The current resource is nil so we will create a new share")
+ create_share
+ else
+ Chef::Log.debug("The current resource was not nil so we will update an existing share")
+ update_share
+ end
+
+ # creating the share does not set permissions so we need to update
+ update_permissions
+ end
+ end
+
+ action :delete do
+ description "Delete an existing Windows share."
+
+ if current_resource.nil?
+ Chef::Log.debug("#{new_resource.share_name} does not exist - nothing to do")
+ else
+ converge_by("delete #{new_resource.share_name}") do
+ delete_share
+ end
+ end
+ end
+
+ action_class do
+ private
+
+ def different_path?
+ return false if current_resource.nil? # going from nil to something isn't different for our concerns
+ return false if current_resource.path == Chef::Util::PathHelper.cleanpath(new_resource.path)
+
+ true
+ end
+
+ def delete_share
+ delete_command = "Remove-SmbShare -Name '#{new_resource.share_name}' -Force"
+
+ Chef::Log.debug("Running '#{delete_command}' to remove the share")
+ powershell_exec!(delete_command)
+ end
+
+ def update_share
+ update_command = "Set-SmbShare -Name '#{new_resource.share_name}' -Description '#{new_resource.description}' -ConcurrentUserLimit #{new_resource.concurrent_user_limit} -CATimeout #{new_resource.ca_timeout} -EncryptData:#{bool_string(new_resource.encrypt_data)} -ContinuouslyAvailable:#{bool_string(new_resource.continuously_available)} -Force"
+ update_command << " -ScopeName #{new_resource.scope_name}" unless new_resource.scope_name == "*" # passing * causes the command to fail
+ update_command << " -Temporary:#{bool_string(new_resource.temporary)}" if new_resource.temporary # only set true
+
+ Chef::Log.debug("Running '#{update_command}' to update the share")
+ powershell_exec!(update_command)
+ end
+
+ def create_share
+ raise "#{new_resource.path} is missing or not a directory. Shares cannot be created if the path doesn't first exist." unless ::File.directory? new_resource.path
+
+ share_cmd = "New-SmbShare -Name '#{new_resource.share_name}' -Path '#{Chef::Util::PathHelper.cleanpath(new_resource.path)}' -Description '#{new_resource.description}' -ConcurrentUserLimit #{new_resource.concurrent_user_limit} -CATimeout #{new_resource.ca_timeout} -EncryptData:#{bool_string(new_resource.encrypt_data)} -ContinuouslyAvailable:#{bool_string(new_resource.continuously_available)}"
+ share_cmd << " -ScopeName #{new_resource.scope_name}" unless new_resource.scope_name == "*" # passing * causes the command to fail
+ share_cmd << " -Temporary:#{bool_string(new_resource.temporary)}" if new_resource.temporary # only set true
+
+ Chef::Log.debug("Running '#{share_cmd}' to create the share")
+ powershell_exec!(share_cmd)
+
+ # New-SmbShare adds the "Everyone" user with read access no matter what so we need to remove it
+ # before we add our permissions
+ revoke_user_permissions(["Everyone"])
+ end
+
+ # determine what users in the current state don't exist in the desired state
+ # users/groups will have their permissions updated with the same command that
+ # sets it, but removes must be performed with Revoke-SmbShareAccess
+ def users_to_revoke
+ @users_to_revoke ||= begin
+ # if the resource doesn't exist then nothing needs to be revoked
+ if current_resource.nil?
+ []
+ else # if it exists then calculate the current to new resource diffs
+ (current_resource.full_users + current_resource.change_users + current_resource.read_users) - (new_resource.full_users + new_resource.change_users + new_resource.read_users)
+ end
+ end
+ end
+
+ # update existing permissions on a share
+ def update_permissions
+ # revoke any users that had something, but now has nothing
+ revoke_user_permissions(users_to_revoke) unless users_to_revoke.empty?
+
+ # set permissions for each of the permission types
+ %w{full read change}.each do |perm_type|
+ # set permissions for a brand new share OR
+ # update permissions if the current state and desired state differ
+ next unless permissions_need_update?(perm_type)
+
+ grant_command = "Grant-SmbShareAccess -Name '#{new_resource.share_name}' -AccountName \"#{new_resource.send("#{perm_type}_users").join('","')}\" -Force -AccessRight #{perm_type}"
+
+ Chef::Log.debug("Running '#{grant_command}' to update the share permissions")
+ powershell_exec!(grant_command)
+ end
+ end
+
+ # determine if permissions need to be updated.
+ # Brand new share with no permissions defined: no
+ # Brand new share with permissions defined: yes
+ # Existing share with differing permissions: yes
+ #
+ # @param [String] type the permissions type (Full, Read, or Change)
+ def permissions_need_update?(type)
+ property_name = "#{type}_users"
+
+ # brand new share, but nothing to set
+ return false if current_resource.nil? && new_resource.send(property_name).empty?
+
+ # brand new share with new permissions to set
+ return true if current_resource.nil? && !new_resource.send(property_name).empty?
+
+ # there's a difference between the current and desired state
+ return true unless (new_resource.send(property_name) - current_resource.send(property_name)).empty?
+
+ # anything else
+ false
+ end
+
+ # revoke user permissions from a share
+ # @param [Array] users
+ def revoke_user_permissions(users)
+ revoke_command = "Revoke-SmbShareAccess -Name '#{new_resource.share_name}' -AccountName \"#{users.join('","')}\" -Force"
+ Chef::Log.debug("Running '#{revoke_command}' to revoke share permissions")
+ powershell_exec!(revoke_command)
+ end
+
+ # convert True/False into "$True" & "$False"
+ def bool_string(bool)
+ # bool ? 1 : 0
+ bool ? "$true" : "$false"
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_shortcut.rb b/lib/chef/resource/windows_shortcut.rb
new file mode 100644
index 0000000000..f2264445ba
--- /dev/null
+++ b/lib/chef/resource/windows_shortcut.rb
@@ -0,0 +1,90 @@
+#
+# Author:: Doug MacEachern <dougm@vmware.com>
+# Copyright:: 2010-2018, VMware, Inc.
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class WindowsShortcut < Chef::Resource
+ unified_mode true
+
+ provides(:windows_shortcut) { true }
+
+ description "Use the **windows_shortcut** resource to create shortcut files on Windows."
+ introduced "14.0"
+ examples <<~DOC
+ **Create a shortcut with a description**:
+
+ ```ruby
+ windows_shortcut 'C:\\shortcut_dir.lnk' do
+ target 'C:\\original_dir'
+ description 'Make a shortcut to C:\\original_dir'
+ end
+ ```
+ DOC
+
+ property :shortcut_name, String,
+ description: "An optional property to set the shortcut name if it differs from the resource block's name.",
+ name_property: true
+
+ property :target, String,
+ description: "The destination that the shortcut links to."
+
+ property :arguments, String,
+ description: "Arguments to pass to the target when the shortcut is executed."
+
+ property :description, String,
+ description: "The description of the shortcut"
+
+ property :cwd, String,
+ description: "Working directory to use when the target is executed."
+
+ property :iconlocation, String,
+ description: "Icon to use for the shortcut. Accepts the format of `path, index`, where index is the icon file to use. See Microsoft's [documentation](https://msdn.microsoft.com/en-us/library/3s9bx7at.aspx) for details"
+
+ load_current_value do |desired|
+ require "win32ole" if RUBY_PLATFORM.match?(/mswin|mingw32|windows/)
+
+ link = WIN32OLE.new("WScript.Shell").CreateShortcut(desired.shortcut_name)
+ name desired.shortcut_name
+ target(link.TargetPath)
+ arguments(link.Arguments)
+ description(link.Description)
+ cwd(link.WorkingDirectory)
+ iconlocation(link.IconLocation)
+ end
+
+ action :create do
+ description "Create or modify a Windows shortcut."
+
+ converge_if_changed do
+ converge_by "creating shortcut #{new_resource.shortcut_name}" do
+ link = WIN32OLE.new("WScript.Shell").CreateShortcut(new_resource.shortcut_name)
+ link.TargetPath = new_resource.target unless new_resource.target.nil?
+ link.Arguments = new_resource.arguments unless new_resource.arguments.nil?
+ link.Description = new_resource.description unless new_resource.description.nil?
+ link.WorkingDirectory = new_resource.cwd unless new_resource.cwd.nil?
+ link.IconLocation = new_resource.iconlocation unless new_resource.iconlocation.nil?
+ # ignoring: WindowStyle, Hotkey
+ link.Save
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_task.rb b/lib/chef/resource/windows_task.rb
new file mode 100644
index 0000000000..29bade29ce
--- /dev/null
+++ b/lib/chef/resource/windows_task.rb
@@ -0,0 +1,1070 @@
+#
+# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>)
+# Copyright:: Copyright (c) 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-utils" unless defined?(ChefUtils::CANARY)
+require_relative "../resource"
+require_relative "../win32/security" if ChefUtils.windows_ruby?
+autoload :ISO8601, "iso8601" if ChefUtils.windows_ruby?
+require_relative "../util/path_helper"
+require "win32/taskscheduler" if ChefUtils.windows_ruby?
+
+class Chef
+ class Resource
+ class WindowsTask < Chef::Resource
+ unified_mode true
+
+ provides(:windows_task) { true }
+
+ description "Use the **windows_task** resource to create, delete or run a Windows scheduled task."
+ introduced "13.0"
+ examples <<~DOC
+ **Create a scheduled task to run every 15 minutes as the Administrator user**:
+
+ ```ruby
+ windows_task 'chef-client' do
+ user 'Administrator'
+ password 'password'
+ command 'chef-client'
+ run_level :highest
+ frequency :minute
+ frequency_modifier 15
+ end
+ ```
+
+ **Create a scheduled task to run every 2 days**:
+
+ ``` ruby
+ windows_task 'chef-client' do
+ command 'chef-client'
+ run_level :highest
+ frequency :daily
+ frequency_modifier 2
+ end
+ ```
+
+ **Create a scheduled task to run on specific days of the week**:
+
+ ```ruby
+ windows_task 'chef-client' do
+ command 'chef-client'
+ run_level :highest
+ frequency :weekly
+ day 'Mon, Thu'
+ end
+ ```
+
+ **Create a scheduled task to run only once**:
+
+ ```ruby
+ windows_task 'chef-client' do
+ command 'chef-client'
+ run_level :highest
+ frequency :once
+ start_time '16:10'
+ end
+ ```
+
+ **Create a scheduled task to run on current day every 3 weeks and delay upto 1 min**:
+
+ ```ruby
+ windows_task 'chef-client' do
+ command 'chef-client'
+ run_level :highest
+ frequency :weekly
+ frequency_modifier 3
+ random_delay '60'
+ end
+ ```
+
+ **Create a scheduled task to run weekly starting on Dec 28th 2018**:
+
+ ```ruby
+ windows_task 'chef-client 8' do
+ command 'chef-client'
+ run_level :highest
+ frequency :weekly
+ start_day '12/28/2018'
+ end
+ ```
+
+ **Create a scheduled task to run every Monday, Friday every 2 weeks**:
+
+ ```ruby
+ windows_task 'chef-client' do
+ command 'chef-client'
+ run_level :highest
+ frequency :weekly
+ frequency_modifier 2
+ day 'Mon, Fri'
+ end
+ ```
+
+ **Create a scheduled task to run when computer is idle with idle duration 20 min**:
+
+ ```ruby
+ windows_task 'chef-client' do
+ command 'chef-client'
+ run_level :highest
+ frequency :on_idle
+ idle_time 20
+ end
+ ```
+
+ **Delete a task named "old task"**:
+ ```ruby
+ windows_task 'old task' do
+ action :delete
+ end
+ ```
+
+ **Enable a task named "chef-client"**:
+ ```ruby
+ windows_task 'chef-client' do
+ action :enable
+ end
+ ```
+
+ **Disable a task named "ProgramDataUpdater" with TaskPath "\\Microsoft\\Windows\\Application Experience\\ProgramDataUpdater"**
+ ```ruby
+ windows_task '\\Microsoft\\Windows\\Application Experience\\ProgramDataUpdater' do
+ action :disable
+ end
+ ```
+ DOC
+
+ allowed_actions :create, :delete, :run, :end, :enable, :disable, :change
+ default_action :create
+
+ property :task_name, String, regex: [%r{\A[^/\:\*\?\<\>\|]+\z}],
+ description: "An optional property to set the task name if it differs from the resource block's name. Example: `Task Name` or `/Task Name`",
+ name_property: true
+
+ property :command, String,
+ description: "The command to be executed by the windows scheduled task."
+
+ property :cwd, String,
+ description: "The directory the task will be run from."
+
+ property :user, String,
+ description: "The user to run the task as.",
+ default: lazy { Chef::ReservedNames::Win32::Security::SID.LocalSystem.account_simple_name if ChefUtils.windows_ruby? },
+ default_description: "The localized SYSTEM user for the node."
+
+ property :password, String,
+ description: "The user's password. The user property must be set if using this property."
+
+ property :run_level, Symbol, equal_to: %i{highest limited},
+ description: "Run with `:limited` or `:highest` privileges.",
+ default: :limited
+
+ property :force, [TrueClass, FalseClass],
+ description: "When used with create, will update the task.",
+ default: false
+
+ property :interactive_enabled, [TrueClass, FalseClass],
+ description: "Allow task to run interactively or non-interactively. Requires user and password to also be set.",
+ default: false
+
+ property :frequency_modifier, [Integer, String],
+ default: 1
+
+ property :frequency, Symbol, equal_to: %i{minute hourly daily weekly monthly once on_logon onstart on_idle none},
+ description: "The frequency with which to run the task."
+
+ property :start_day, String,
+ description: "Specifies the first date on which the task runs in **MM/DD/YYYY** format.",
+ default_description: "The current date."
+
+ property :start_time, String,
+ description: "Specifies the start time to run the task, in **HH:mm** format."
+
+ property :day, [String, Integer],
+ description: "The day(s) on which the task runs."
+
+ property :months, String,
+ description: "The Months of the year on which the task runs, such as: `JAN, FEB` or `*`. Multiple months should be comma delimited. e.g. `Jan, Feb, Mar, Dec`."
+
+ property :idle_time, Integer,
+ description: "For `:on_idle` frequency, the time (in minutes) without user activity that must pass to trigger the task, from `1` - `999`."
+
+ property :random_delay, [String, Integer],
+ description: "Delays the task up to a given time (in seconds)."
+
+ property :execution_time_limit, [String, Integer],
+ description: "The maximum time the task will run. This field accepts either seconds or an ISO8601 duration value.",
+ default: "PT72H",
+ default_description: "PT72H (72 hours in ISO8601 duration format)"
+
+ property :minutes_duration, [String, Integer],
+ description: ""
+
+ property :minutes_interval, [String, Integer],
+ description: ""
+
+ property :priority, Integer,
+ description: "Use to set Priority Levels range from 0 to 10.",
+ default: 7, callbacks: { "should be in range of 0 to 10" => proc { |v| v >= 0 && v <= 10 } }
+
+ property :disallow_start_if_on_batteries, [TrueClass, FalseClass],
+ introduced: "14.4", default: false,
+ description: "Disallow start of the task if the system is running on battery power."
+
+ property :stop_if_going_on_batteries, [TrueClass, FalseClass],
+ introduced: "14.4", default: false,
+ description: "Scheduled task option when system is switching on battery."
+
+ property :description, String,
+ introduced: "14.7",
+ description: "The task description."
+
+ property :start_when_available, [TrueClass, FalseClass],
+ introduced: "14.15", default: false,
+ description: "To start the task at any time after its scheduled time has passed."
+
+ attr_accessor :exists, :task, :command_arguments
+
+ VALID_WEEK_DAYS = %w{ mon tue wed thu fri sat sun * }.freeze
+ VALID_DAYS_OF_MONTH = ("1".."31").to_a << "last" << "lastday"
+ VALID_MONTHS = %w{JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC *}.freeze
+ VALID_WEEKS = %w{FIRST SECOND THIRD FOURTH LAST LASTDAY}.freeze
+
+ def after_created
+ if random_delay
+ validate_random_delay(random_delay, frequency)
+ random_delay(sec_to_min(random_delay))
+ end
+
+ if execution_time_limit
+ execution_time_limit(259200) if execution_time_limit == "PT72H"
+ raise ArgumentError, "Invalid value passed for `execution_time_limit`. Please pass seconds as an Integer (e.g. 60) or a String with numeric values only (e.g. '60')." unless numeric_value_in_string?(execution_time_limit)
+
+ execution_time_limit(sec_to_min(execution_time_limit))
+ end
+
+ validate_frequency(frequency) if action.include?(:create) || action.include?(:change)
+ validate_start_time(start_time, frequency)
+ validate_start_day(start_day, frequency) if start_day
+ validate_user_and_password(user, password)
+ validate_create_frequency_modifier(frequency, frequency_modifier) if frequency_modifier
+ validate_create_day(day, frequency, frequency_modifier) if day
+ validate_create_months(months, frequency) if months
+ validate_frequency_monthly(frequency_modifier, months, day) if frequency == :monthly
+ validate_idle_time(idle_time, frequency)
+ idempotency_warning_for_frequency_weekly(day, start_day) if frequency == :weekly
+ end
+
+ private
+
+ ## Resource is not idempotent when day, start_day is not provided with frequency :weekly
+ ## we set start_day when not given by user as current date based on which we set the day property for current current date day is monday ..
+ ## we set the monday as the day so at next run when new_resource.day is nil and current_resource day is monday due to which update gets called
+ def idempotency_warning_for_frequency_weekly(day, start_day)
+ if start_day.nil? && day.nil?
+ logger.warn "To maintain idempotency for frequency :weekly provide start_day, start_time and day."
+ end
+ end
+
+ # Validate the passed value is numeric values only if it is a string
+ def numeric_value_in_string?(val)
+ return true if Integer(val)
+ rescue ArgumentError
+ false
+ end
+
+ def validate_frequency(frequency)
+ if frequency.nil? || !(%i{minute hourly daily weekly monthly once on_logon onstart on_idle none}.include?(frequency))
+ raise ArgumentError, "Frequency needs to be provided. Valid frequencies are :minute, :hourly, :daily, :weekly, :monthly, :once, :on_logon, :onstart, :on_idle, :none."
+ end
+ end
+
+ def validate_frequency_monthly(frequency_modifier, months, day)
+ # validates the frequency :monthly and raises error if frequency_modifier is first, second, third etc and day is not provided
+ if (frequency_modifier != 1) && (frequency_modifier_includes_days_of_weeks?(frequency_modifier)) && !(day)
+ raise ArgumentError, "Please select day on which you want to run the task e.g. 'Mon, Tue'. Multiple values must be separated by comma."
+ end
+
+ # frequency_modifier 2-12 is used to set every (n) months, so using :months property with frequency_modifier is not valid since they both used to set months.
+ # Not checking value 1 here for frequency_modifier since we are setting that as default value it won't break anything since preference is given to months property
+ if (frequency_modifier.to_i.between?(2, 12)) && !(months.nil?)
+ raise ArgumentError, "For frequency :monthly either use property months or frequency_modifier to set months."
+ end
+ end
+
+ # returns true if frequency_modifier has values First, second, third, fourth, last, lastday
+ def frequency_modifier_includes_days_of_weeks?(frequency_modifier)
+ frequency_modifier = frequency_modifier.to_s.split(",")
+ frequency_modifier.map! { |value| value.strip.upcase }
+ (frequency_modifier - VALID_WEEKS).empty?
+ end
+
+ def validate_random_delay(random_delay, frequency)
+ if %i{on_logon onstart on_idle none}.include? frequency
+ raise ArgumentError, "`random_delay` property is supported only for frequency :once, :minute, :hourly, :daily, :weekly and :monthly"
+ end
+
+ raise ArgumentError, "Invalid value passed for `random_delay`. Please pass seconds as an Integer (e.g. 60) or a String with numeric values only (e.g. '60')." unless numeric_value_in_string?(random_delay)
+ end
+
+ # @todo when we drop ruby 2.3 support this should be converted to .match?() instead of =~f
+ def validate_start_day(start_day, frequency)
+ if start_day && frequency == :none
+ raise ArgumentError, "`start_day` property is not supported with frequency: #{frequency}"
+ end
+
+ # make sure the start_day is in MM/DD/YYYY format: http://rubular.com/r/cgjHemtWl5
+ if start_day
+ raise ArgumentError, "`start_day` property must be in the MM/DD/YYYY format." unless %r{^(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.](19|20)\d\d$}.match?(start_day)
+ end
+ end
+
+ # @todo when we drop ruby 2.3 support this should be converted to .match?() instead of =~
+ def validate_start_time(start_time, frequency)
+ if start_time
+ raise ArgumentError, "`start_time` property is not supported with `frequency :none`" if frequency == :none
+ raise ArgumentError, "`start_time` property must be in the HH:mm format (e.g. 6:20pm -> 18:20)." unless /^[0-2][0-9]:[0-5][0-9]$/.match?(start_time)
+ else
+ raise ArgumentError, "`start_time` needs to be provided with `frequency :once`" if frequency == :once
+ end
+ end
+
+ # System users will not require a password
+ # Other users will require a password if the task is non-interactive.
+ #
+ # @param [String] user
+ # @param [String] password
+ #
+ def validate_user_and_password(user, password)
+ if non_system_user?(user)
+ if password.nil? && !interactive_enabled
+ raise ArgumentError, "Please provide a password or check if this task needs to be interactive! Valid passwordless users are: '#{Chef::ReservedNames::Win32::Security::SID::SYSTEM_USER.join("', '")}'"
+ end
+ else
+ unless password.nil?
+ raise ArgumentError, "Password is not required for system users."
+ end
+ end
+ end
+
+ # Password is not required for system user and required for non-system user.
+ def password_required?(user)
+ @password_required ||= (!user.nil? && !Chef::ReservedNames::Win32::Security::SID.system_user?(user))
+ end
+
+ alias non_system_user? password_required?
+
+ def validate_create_frequency_modifier(frequency, frequency_modifier)
+ if (%i{on_logon onstart on_idle none}.include?(frequency)) && ( frequency_modifier != 1)
+ raise ArgumentError, "frequency_modifier property not supported with frequency :#{frequency}"
+ end
+
+ if frequency == :monthly
+ unless (1..12).cover?(frequency_modifier.to_i) || frequency_modifier_includes_days_of_weeks?(frequency_modifier)
+ raise ArgumentError, "frequency_modifier value #{frequency_modifier} is invalid. Valid values for :monthly frequency are 1 - 12, 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST'."
+ end
+ else
+ unless frequency.nil? || frequency_modifier.nil?
+ frequency_modifier = frequency_modifier.to_i
+ min = 1
+ max = case frequency
+ when :minute
+ 1439
+ when :hourly
+ 23
+ when :daily
+ 365
+ when :weekly
+ 52
+ else
+ min
+ end
+ unless frequency_modifier.between?(min, max)
+ raise ArgumentError, "frequency_modifier value #{frequency_modifier} is invalid. Valid values for :#{frequency} frequency are #{min} - #{max}."
+ end
+ end
+ end
+ end
+
+ def validate_create_day(day, frequency, frequency_modifier)
+ raise ArgumentError, "day property is only valid for tasks that run monthly or weekly" unless %i{weekly monthly}.include?(frequency)
+
+ # This has been verified with schtask.exe https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/schtasks#d-dayday--
+ # verified with earlier code if day "*" is given with frequency it raised exception Invalid value for /D option
+ raise ArgumentError, "day wild card (*) is only valid with frequency :weekly" if frequency == :monthly && day == "*"
+
+ if day.is_a?(String) && day.to_i.to_s != day
+ days = day.split(",")
+ if days_includes_days_of_months?(days)
+ # Following error will be raise if day is set as 1-31 and frequency is selected as :weekly since those values are valid with only frequency :monthly
+ raise ArgumentError, "day values 1-31 or last is only valid with frequency :monthly" if frequency == :weekly
+ else
+ days.map! { |day| day.to_s.strip.downcase }
+ unless (days - VALID_WEEK_DAYS).empty?
+ raise ArgumentError, "day property invalid. Only valid values are: #{VALID_WEEK_DAYS.map(&:upcase).join(", ")}. Multiple values must be separated by a comma."
+ end
+ end
+ end
+ end
+
+ def validate_create_months(months, frequency)
+ raise ArgumentError, "months property is only valid for tasks that run monthly" if frequency != :monthly
+
+ if months.is_a?(String)
+ months = months.split(",")
+ months.map! { |month| month.strip.upcase }
+ unless (months - VALID_MONTHS).empty?
+ raise ArgumentError, "months property invalid. Only valid values are: #{VALID_MONTHS.join(", ")}. Multiple values must be separated by a comma."
+ end
+ end
+ end
+
+ # This method returns true if day has values from 1-31 which is a days of moths and used with frequency :monthly
+ def days_includes_days_of_months?(days)
+ days.map! { |day| day.to_s.strip.downcase }
+ (days - VALID_DAYS_OF_MONTH).empty?
+ end
+
+ def validate_idle_time(idle_time, frequency)
+ if !idle_time.nil? && frequency != :on_idle
+ raise ArgumentError, "idle_time property is only valid for tasks that run on_idle"
+ end
+ if idle_time.nil? && frequency == :on_idle
+ raise ArgumentError, "idle_time value should be set for :on_idle frequency."
+ end
+ unless idle_time.nil? || idle_time > 0 && idle_time <= 999
+ raise ArgumentError, "idle_time value #{idle_time} is invalid. Valid values for :on_idle frequency are 1 - 999."
+ end
+ end
+
+ # Converts the number of seconds to an ISO8601 duration format and returns it.
+ # Ref : https://github.com/arnau/ISO8601/blob/master/lib/iso8601/duration.rb#L18-L23
+ # e.g.
+ # ISO8601::Duration.new(65707200).to_s
+ # returns 'PT65707200S'
+ def sec_to_dur(seconds)
+ ISO8601::Duration.new(seconds.to_i).to_s
+ end
+
+ def sec_to_min(seconds)
+ seconds.to_i / 60
+ end
+
+ action_class do
+ if ChefUtils.windows_ruby?
+ include ::Win32
+
+ MONTHS = {
+ JAN: ::Win32::TaskScheduler::JANUARY,
+ FEB: ::Win32::TaskScheduler::FEBRUARY,
+ MAR: ::Win32::TaskScheduler::MARCH,
+ APR: ::Win32::TaskScheduler::APRIL,
+ MAY: ::Win32::TaskScheduler::MAY,
+ JUN: ::Win32::TaskScheduler::JUNE,
+ JUL: ::Win32::TaskScheduler::JULY,
+ AUG: ::Win32::TaskScheduler::AUGUST,
+ SEP: ::Win32::TaskScheduler::SEPTEMBER,
+ OCT: ::Win32::TaskScheduler::OCTOBER,
+ NOV: ::Win32::TaskScheduler::NOVEMBER,
+ DEC: ::Win32::TaskScheduler::DECEMBER,
+ }.freeze
+
+ DAYS_OF_WEEK = { MON: ::Win32::TaskScheduler::MONDAY,
+ TUE: ::Win32::TaskScheduler::TUESDAY,
+ WED: ::Win32::TaskScheduler::WEDNESDAY,
+ THU: ::Win32::TaskScheduler::THURSDAY,
+ FRI: ::Win32::TaskScheduler::FRIDAY,
+ SAT: ::Win32::TaskScheduler::SATURDAY,
+ SUN: ::Win32::TaskScheduler::SUNDAY }.freeze
+
+ WEEKS_OF_MONTH = {
+ FIRST: ::Win32::TaskScheduler::FIRST_WEEK,
+ SECOND: ::Win32::TaskScheduler::SECOND_WEEK,
+ THIRD: ::Win32::TaskScheduler::THIRD_WEEK,
+ FOURTH: ::Win32::TaskScheduler::FOURTH_WEEK,
+ }.freeze
+
+ DAYS_OF_MONTH = {
+ 1 => ::Win32::TaskScheduler::TASK_FIRST,
+ 2 => ::Win32::TaskScheduler::TASK_SECOND,
+ 3 => ::Win32::TaskScheduler::TASK_THIRD,
+ 4 => ::Win32::TaskScheduler::TASK_FOURTH,
+ 5 => ::Win32::TaskScheduler::TASK_FIFTH,
+ 6 => ::Win32::TaskScheduler::TASK_SIXTH,
+ 7 => ::Win32::TaskScheduler::TASK_SEVENTH,
+ 8 => ::Win32::TaskScheduler::TASK_EIGHTH,
+ # cspell:disable-next-line
+ 9 => ::Win32::TaskScheduler::TASK_NINETH,
+ 10 => ::Win32::TaskScheduler::TASK_TENTH,
+ 11 => ::Win32::TaskScheduler::TASK_ELEVENTH,
+ 12 => ::Win32::TaskScheduler::TASK_TWELFTH,
+ 13 => ::Win32::TaskScheduler::TASK_THIRTEENTH,
+ 14 => ::Win32::TaskScheduler::TASK_FOURTEENTH,
+ 15 => ::Win32::TaskScheduler::TASK_FIFTEENTH,
+ 16 => ::Win32::TaskScheduler::TASK_SIXTEENTH,
+ 17 => ::Win32::TaskScheduler::TASK_SEVENTEENTH,
+ 18 => ::Win32::TaskScheduler::TASK_EIGHTEENTH,
+ 19 => ::Win32::TaskScheduler::TASK_NINETEENTH,
+ 20 => ::Win32::TaskScheduler::TASK_TWENTIETH,
+ 21 => ::Win32::TaskScheduler::TASK_TWENTY_FIRST,
+ 22 => ::Win32::TaskScheduler::TASK_TWENTY_SECOND,
+ 23 => ::Win32::TaskScheduler::TASK_TWENTY_THIRD,
+ 24 => ::Win32::TaskScheduler::TASK_TWENTY_FOURTH,
+ 25 => ::Win32::TaskScheduler::TASK_TWENTY_FIFTH,
+ 26 => ::Win32::TaskScheduler::TASK_TWENTY_SIXTH,
+ 27 => ::Win32::TaskScheduler::TASK_TWENTY_SEVENTH,
+ 28 => ::Win32::TaskScheduler::TASK_TWENTY_EIGHTH,
+ 29 => ::Win32::TaskScheduler::TASK_TWENTY_NINTH,
+ # cspell:disable-next-line
+ 30 => ::Win32::TaskScheduler::TASK_THIRTYETH,
+ 31 => ::Win32::TaskScheduler::TASK_THIRTY_FIRST,
+ }.freeze
+
+ PRIORITY = { "critical" => 0, "highest" => 1, "above_normal_2" => 2 , "above_normal_3" => 3, "normal_4" => 4,
+ "normal_5" => 5, "normal_6" => 6, "below_normal_7" => 7, "below_normal_8" => 8, "lowest" => 9, "idle" => 10 }.freeze
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::WindowsTask.new(new_resource.name)
+ task = ::Win32::TaskScheduler.new(new_resource.task_name, nil, "\\", false)
+ @current_resource.exists = task.exists?(new_resource.task_name)
+ if @current_resource.exists
+ task.get_task(new_resource.task_name)
+ @current_resource.task = task
+ pathed_task_name = new_resource.task_name.start_with?('\\') ? new_resource.task_name : "\\#{new_resource.task_name}"
+ @current_resource.task_name(pathed_task_name)
+ end
+ @current_resource
+ end
+
+ # separated command arguments from :command property
+ def set_command_and_arguments
+ cmd, *args = Chef::Util::PathHelper.split_args(new_resource.command)
+ new_resource.command = cmd
+ new_resource.command_arguments = args.join(" ")
+ end
+
+ def set_start_day_and_time
+ new_resource.start_day = Time.now.strftime("%m/%d/%Y") unless new_resource.start_day
+ new_resource.start_time = Time.now.strftime("%H:%M") unless new_resource.start_time
+ end
+
+ def update_task(task)
+ converge_by("#{new_resource} task updated") do
+ task.set_account_information(new_resource.user, new_resource.password, new_resource.interactive_enabled)
+ task.application_name = new_resource.command if new_resource.command
+ task.parameters = new_resource.command_arguments if new_resource.command_arguments
+ task.working_directory = new_resource.cwd if new_resource.cwd
+ task.trigger = trigger unless new_resource.frequency == :none
+ task.configure_settings(config_settings)
+ task.creator = new_resource.user
+ task.description = new_resource.description unless new_resource.description.nil?
+ task.configure_principals(principal_settings)
+ end
+ end
+
+ def trigger
+ start_month, start_day, start_year = new_resource.start_day.to_s.split("/")
+ start_hour, start_minute = new_resource.start_time.to_s.split(":")
+ # TODO currently end_month, end_year and end_year needs to be set to 0. If not set win32-taskscheduler throwing nil into integer error.
+ trigger_hash = {
+ start_year: start_year.to_i,
+ start_month: start_month.to_i,
+ start_day: start_day.to_i,
+ start_hour: start_hour.to_i,
+ start_minute: start_minute.to_i,
+ end_month: 0,
+ end_day: 0,
+ end_year: 0,
+ trigger_type: trigger_type,
+ type: type,
+ random_minutes_interval: new_resource.random_delay,
+ }
+
+ if new_resource.frequency == :minute
+ trigger_hash[:minutes_interval] = new_resource.frequency_modifier
+ end
+
+ if new_resource.frequency == :hourly
+ minutes = convert_hours_in_minutes(new_resource.frequency_modifier.to_i)
+ trigger_hash[:minutes_interval] = minutes
+ end
+
+ if new_resource.minutes_interval
+ trigger_hash[:minutes_interval] = new_resource.minutes_interval
+ end
+
+ if new_resource.minutes_duration
+ trigger_hash[:minutes_duration] = new_resource.minutes_duration
+ end
+
+ if trigger_type == ::Win32::TaskScheduler::MONTHLYDOW && frequency_modifier_contains_last_week?(new_resource.frequency_modifier)
+ trigger_hash[:run_on_last_week_of_month] = true
+ else
+ trigger_hash[:run_on_last_week_of_month] = false
+ end
+
+ if trigger_type == ::Win32::TaskScheduler::MONTHLYDATE && day_includes_last_or_lastday?(new_resource.day)
+ trigger_hash[:run_on_last_day_of_month] = true
+ else
+ trigger_hash[:run_on_last_day_of_month] = false
+ end
+ trigger_hash
+ end
+
+ def frequency_modifier_contains_last_week?(frequency_modifier)
+ frequency_modifier = frequency_modifier.to_s.split(",")
+ frequency_modifier.map! { |value| value.strip.upcase }
+ frequency_modifier.include?("LAST")
+ end
+
+ def day_includes_last_or_lastday?(day)
+ day = day.to_s.split(",")
+ day.map! { |value| value.strip.upcase }
+ day.include?("LAST") || day.include?("LASTDAY")
+ end
+
+ def convert_hours_in_minutes(hours)
+ hours.to_i * 60 if hours
+ end
+
+ # TODO : Try to optimize this method
+ # known issue : Since start_day and time is not mandatory while updating weekly frequency for which start_day is not mentioned by user idempotency
+ # is not getting maintained as new_resource.start_day is nil and we fetch the day of week from start_day to set and its currently coming as nil and don't match with current_task
+ def task_needs_update?(task)
+ flag = false
+ if new_resource.frequency == :none
+ flag = (task.author != new_resource.user ||
+ task.application_name != new_resource.command ||
+ description_needs_update?(task) ||
+ task.parameters != new_resource.command_arguments.to_s ||
+ task.principals[:run_level] != run_level ||
+ task.settings[:disallow_start_if_on_batteries] != new_resource.disallow_start_if_on_batteries ||
+ task.settings[:stop_if_going_on_batteries] != new_resource.stop_if_going_on_batteries ||
+ task.settings[:start_when_available] != new_resource.start_when_available)
+ else
+ current_task_trigger = task.trigger(0)
+ new_task_trigger = trigger
+ flag = (ISO8601::Duration.new(task.idle_settings[:idle_duration])) != (ISO8601::Duration.new(new_resource.idle_time * 60)) if new_resource.frequency == :on_idle
+ flag = (ISO8601::Duration.new(task.execution_time_limit)) != (ISO8601::Duration.new(new_resource.execution_time_limit * 60)) unless new_resource.execution_time_limit.nil?
+
+ # if trigger not found updating the task to add the trigger
+ if current_task_trigger.nil?
+ flag = true
+ else
+ flag = true if start_day_updated?(current_task_trigger, new_task_trigger) == true ||
+ start_time_updated?(current_task_trigger, new_task_trigger) == true ||
+ current_task_trigger[:trigger_type] != new_task_trigger[:trigger_type] ||
+ current_task_trigger[:type] != new_task_trigger[:type] ||
+ current_task_trigger[:random_minutes_interval].to_i != new_task_trigger[:random_minutes_interval].to_i ||
+ current_task_trigger[:minutes_interval].to_i != new_task_trigger[:minutes_interval].to_i ||
+ task.author.to_s.casecmp(new_resource.user.to_s) != 0 ||
+ task.application_name != new_resource.command ||
+ description_needs_update?(task) ||
+ task.parameters != new_resource.command_arguments.to_s ||
+ task.working_directory != new_resource.cwd.to_s ||
+ task.principals[:logon_type] != logon_type ||
+ task.principals[:run_level] != run_level ||
+ PRIORITY[task.priority] != new_resource.priority ||
+ task.settings[:disallow_start_if_on_batteries] != new_resource.disallow_start_if_on_batteries ||
+ task.settings[:stop_if_going_on_batteries] != new_resource.stop_if_going_on_batteries ||
+ task.settings[:start_when_available] != new_resource.start_when_available
+ if trigger_type == ::Win32::TaskScheduler::MONTHLYDATE
+ flag = true if current_task_trigger[:run_on_last_day_of_month] != new_task_trigger[:run_on_last_day_of_month]
+ end
+
+ if trigger_type == ::Win32::TaskScheduler::MONTHLYDOW
+ flag = true if current_task_trigger[:run_on_last_week_of_month] != new_task_trigger[:run_on_last_week_of_month]
+ end
+ end
+ end
+ flag
+ end
+
+ def start_day_updated?(current_task_trigger, new_task_trigger)
+ ( new_resource.start_day && (current_task_trigger[:start_year].to_i != new_task_trigger[:start_year] ||
+ current_task_trigger[:start_month].to_i != new_task_trigger[:start_month] ||
+ current_task_trigger[:start_day].to_i != new_task_trigger[:start_day]) )
+ end
+
+ def start_time_updated?(current_task_trigger, new_task_trigger)
+ ( new_resource.start_time && ( current_task_trigger[:start_hour].to_i != new_task_trigger[:start_hour] ||
+ current_task_trigger[:start_minute].to_i != new_task_trigger[:start_minute] ) )
+ end
+
+ def trigger_type
+ case new_resource.frequency
+ when :once, :minute, :hourly
+ ::Win32::TaskScheduler::ONCE
+ when :daily
+ ::Win32::TaskScheduler::DAILY
+ when :weekly
+ ::Win32::TaskScheduler::WEEKLY
+ when :monthly
+ # If frequency modifier is set with frequency :monthly we are setting taskscheduler as monthlydow
+ # Ref https://msdn.microsoft.com/en-us/library/windows/desktop/aa382061(v=vs.85).aspx
+ new_resource.frequency_modifier.to_i.between?(1, 12) ? ::Win32::TaskScheduler::MONTHLYDATE : ::Win32::TaskScheduler::MONTHLYDOW
+ when :on_idle
+ ::Win32::TaskScheduler::ON_IDLE
+ when :onstart
+ ::Win32::TaskScheduler::AT_SYSTEMSTART
+ when :on_logon
+ ::Win32::TaskScheduler::AT_LOGON
+ else
+ raise ArgumentError, "Please set frequency"
+ end
+ end
+
+ def type
+ case trigger_type
+ when ::Win32::TaskScheduler::ONCE
+ { once: nil }
+ when ::Win32::TaskScheduler::DAILY
+ { days_interval: new_resource.frequency_modifier.to_i }
+ when ::Win32::TaskScheduler::WEEKLY
+ { weeks_interval: new_resource.frequency_modifier.to_i, days_of_week: days_of_week.to_i }
+ when ::Win32::TaskScheduler::MONTHLYDATE
+ { months: months_of_year.to_i, days: days_of_month.to_i }
+ when ::Win32::TaskScheduler::MONTHLYDOW
+ { months: months_of_year.to_i, days_of_week: days_of_week.to_i, weeks_of_month: weeks_of_month.to_i }
+ when ::Win32::TaskScheduler::ON_IDLE
+ # TODO: handle option for this trigger
+ when ::Win32::TaskScheduler::AT_LOGON
+ # TODO: handle option for this trigger
+ when ::Win32::TaskScheduler::AT_SYSTEMSTART
+ # TODO: handle option for this trigger
+ end
+ end
+
+ # Deleting last from the array of weeks of month since last week is handled in :run_on_last_week_of_month parameter.
+ def weeks_of_month
+ weeks_of_month = []
+ if new_resource.frequency_modifier
+ weeks = new_resource.frequency_modifier.split(",")
+ weeks.map! { |week| week.to_s.strip.upcase }
+ weeks.delete("LAST") if weeks.include?("LAST")
+ weeks_of_month = get_binary_values_from_constants(weeks, WEEKS_OF_MONTH)
+ end
+ weeks_of_month
+ end
+
+ # Deleting the "LAST" and "LASTDAY" from days since last day is handled in :run_on_last_day_of_month parameter.
+ def days_of_month
+ days_of_month = []
+ if new_resource.day
+ days = new_resource.day.to_s.split(",")
+ days.map! { |day| day.to_s.strip.upcase }
+ days.delete("LAST") if days.include?("LAST")
+ days.delete("LASTDAY") if days.include?("LASTDAY")
+ if days - (1..31).to_a
+ days.each do |day|
+ days_of_month << DAYS_OF_MONTH[day.to_i]
+ end
+ days_of_month = days_of_month.size > 1 ? days_of_month.inject(:|) : days_of_month[0]
+ end
+ else
+ days_of_month = DAYS_OF_MONTH[1]
+ end
+ days_of_month
+ end
+
+ def days_of_week
+ if new_resource.day
+ # this line of code is just to support backward compatibility of wild card *
+ new_resource.day = "mon, tue, wed, thu, fri, sat, sun" if new_resource.day == "*" && new_resource.frequency == :weekly
+ days = new_resource.day.to_s.split(",")
+ days.map! { |day| day.to_s.strip.upcase }
+ weeks_days = get_binary_values_from_constants(days, DAYS_OF_WEEK)
+ else
+ # following condition will make the frequency :weekly idempotent if start_day is not provided by user setting day as the current_resource day
+ if (current_resource) && (current_resource.task) && (current_resource.task.trigger(0)[:type][:days_of_week]) && (new_resource.start_day.nil?)
+ weeks_days = current_resource.task.trigger(0)[:type][:days_of_week]
+ else
+ day = get_day(new_resource.start_day).to_sym if new_resource.start_day
+ DAYS_OF_WEEK[day]
+ end
+ end
+ end
+
+ def months_of_year
+ months_of_year = []
+ if new_resource.frequency_modifier.to_i.between?(1, 12) && !(new_resource.months)
+ new_resource.months = set_months(new_resource.frequency_modifier.to_i)
+ end
+
+ if new_resource.months
+ # this line of code is just to support backward compatibility of wild card *
+ new_resource.months = "jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec" if new_resource.months == "*" && new_resource.frequency == :monthly
+ months = new_resource.months.split(",")
+ months.map! { |month| month.to_s.strip.upcase }
+ months_of_year = get_binary_values_from_constants(months, MONTHS)
+ else
+ MONTHS.each do |key, value|
+ months_of_year << MONTHS[key]
+ end
+ months_of_year = months_of_year.inject(:|)
+ end
+ months_of_year
+ end
+
+ # This values are set for frequency_modifier set as 1-12
+ # This is to give backward compatibility validated this values with earlier code and running schtask.exe
+ # Used this as reference https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/schtasks#d-dayday--
+ def set_months(frequency_modifier)
+ case frequency_modifier
+ when 1
+ "jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec"
+ when 2
+ "feb, apr, jun, aug, oct, dec"
+ when 3
+ "mar, jun, sep, dec"
+ when 4
+ "apr, aug, dec"
+ when 5
+ "may, oct"
+ when 6
+ "jun, dec"
+ when 7
+ "jul"
+ when 8
+ "aug"
+ when 9
+ "sep"
+ when 10
+ "oct"
+ when 11
+ "nov"
+ when 12
+ "dec"
+ end
+ end
+
+ def get_binary_values_from_constants(array_values, constant)
+ data = []
+ array_values.each do |value|
+ value = value.to_sym
+ data << constant[value]
+ end
+ data.size > 1 ? data.inject(:|) : data[0]
+ end
+
+ def run_level
+ case new_resource.run_level
+ when :highest
+ ::Win32::TaskScheduler::TASK_RUNLEVEL_HIGHEST
+ when :limited
+ ::Win32::TaskScheduler::TASK_RUNLEVEL_LUA
+ end
+ end
+
+ # TODO: while creating the configuration settings win32-taskscheduler it accepts execution time limit values in ISO8601 format
+ def config_settings
+ settings = {
+ execution_time_limit: new_resource.execution_time_limit,
+ enabled: true,
+ }
+ settings[:idle_duration] = new_resource.idle_time if new_resource.idle_time
+ settings[:run_only_if_idle] = true if new_resource.idle_time
+ settings[:priority] = new_resource.priority
+ settings[:disallow_start_if_on_batteries] = new_resource.disallow_start_if_on_batteries
+ settings[:stop_if_going_on_batteries] = new_resource.stop_if_going_on_batteries
+ settings[:start_when_available] = new_resource.start_when_available
+ settings
+ end
+
+ def principal_settings
+ settings = {}
+ settings[:run_level] = run_level
+ settings[:logon_type] = logon_type
+ settings
+ end
+
+ def description_needs_update?(task)
+ task.description != new_resource.description unless new_resource.description.nil?
+ end
+
+ def logon_type
+ # Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/aa383566(v=vs.85).aspx
+ # if nothing is passed as logon_type the TASK_LOGON_SERVICE_ACCOUNT is getting set as default so using that for comparison.
+ user_id = new_resource.user.to_s
+ password = new_resource.password.to_s
+ if Chef::ReservedNames::Win32::Security::SID.service_account_user?(user_id)
+ ::Win32::TaskScheduler::TASK_LOGON_SERVICE_ACCOUNT
+ elsif Chef::ReservedNames::Win32::Security::SID.group_user?(user_id)
+ ::Win32::TaskScheduler::TASK_LOGON_GROUP
+ elsif !user_id.empty? && !password.empty?
+ if new_resource.interactive_enabled
+ ::Win32::TaskScheduler::TASK_LOGON_INTERACTIVE_TOKEN
+ else
+ ::Win32::TaskScheduler::TASK_LOGON_PASSWORD
+ end
+ else
+ ::Win32::TaskScheduler::TASK_LOGON_INTERACTIVE_TOKEN
+ end
+ end
+
+ # This method checks if task and command properties exist since those two are mandatory properties to create a schedules task.
+ def basic_validation
+ validate = []
+ validate << "Command" if new_resource.command.nil? || new_resource.command.empty?
+ validate << "Task Name" if new_resource.task_name.nil? || new_resource.task_name.empty?
+ return true if validate.empty?
+
+ raise Chef::Exceptions::ValidationFailed.new "Value for '#{validate.join(", ")}' option cannot be empty"
+ end
+
+ # rubocop:disable Style/StringLiteralsInInterpolation
+ def run_schtasks(task_action, options = {})
+ cmd = "schtasks /#{task_action} /TN \"#{new_resource.task_name}\" "
+ options.each_key do |option|
+ unless option == "TR"
+ cmd += "/#{option} "
+ cmd += "\"#{options[option].to_s.gsub('"', "\\\"")}\" " unless options[option] == ""
+ end
+ end
+ # Appending Task Run [TR] option at the end since appending causing sometimes to append other options in option["TR"] value
+ if options["TR"]
+ cmd += "/TR \"#{options["TR"]} \" " unless task_action == "DELETE"
+ end
+ logger.trace("running: ")
+ logger.trace(" #{cmd}")
+ shell_out!(cmd, returns: [0])
+ end
+ # rubocop:enable Style/StringLiteralsInInterpolation
+
+ def get_day(date)
+ Date.strptime(date, "%m/%d/%Y").strftime("%a").upcase
+ end
+ end
+
+ action :create do
+ set_command_and_arguments if new_resource.command
+
+ if current_resource.exists
+ logger.trace "#{new_resource} task exist."
+ unless (task_needs_update?(current_resource.task)) || (new_resource.force)
+ logger.info "#{new_resource} task does not need updating and force is not specified - nothing to do"
+ return
+ end
+
+ # if start_day and start_time is not set by user current date and time will be set while updating any property
+ set_start_day_and_time unless new_resource.frequency == :none
+ update_task(current_resource.task)
+ else
+ basic_validation
+ set_start_day_and_time
+ converge_by("#{new_resource} task created") do
+ task = ::Win32::TaskScheduler.new
+ if new_resource.frequency == :none
+ task.new_work_item(new_resource.task_name, {}, { user: new_resource.user, password: new_resource.password, interactive: new_resource.interactive_enabled })
+ task.activate(new_resource.task_name)
+ else
+ task.new_work_item(new_resource.task_name, trigger, { user: new_resource.user, password: new_resource.password, interactive: new_resource.interactive_enabled })
+ end
+ task.application_name = new_resource.command
+ task.parameters = new_resource.command_arguments if new_resource.command_arguments
+ task.working_directory = new_resource.cwd if new_resource.cwd
+ task.configure_settings(config_settings)
+ task.configure_principals(principal_settings)
+ task.set_account_information(new_resource.user, new_resource.password, new_resource.interactive_enabled)
+ task.creator = new_resource.user
+ task.description = new_resource.description unless new_resource.description.nil?
+ task.activate(new_resource.task_name)
+ end
+ end
+ end
+
+ action :run do
+ if current_resource.exists
+ logger.trace "#{new_resource} task exists"
+ if current_resource.task.status == "running"
+ logger.info "#{new_resource} task is currently running, skipping run"
+ else
+ converge_by("run scheduled task #{new_resource}") do
+ current_resource.task.run
+ end
+ end
+ else
+ logger.warn "#{new_resource} task does not exist - nothing to do"
+ end
+ end
+
+ action :delete do
+ if current_resource.exists
+ logger.trace "#{new_resource} task exists"
+ converge_by("delete scheduled task #{new_resource}") do
+ ts = ::Win32::TaskScheduler.new
+ ts.delete(current_resource.task_name)
+ end
+ else
+ logger.warn "#{new_resource} task does not exist - nothing to do"
+ end
+ end
+
+ action :end do
+ if current_resource.exists
+ logger.trace "#{new_resource} task exists"
+ if current_resource.task.status != "running"
+ logger.trace "#{new_resource} is not running - nothing to do"
+ else
+ converge_by("#{new_resource} task ended") do
+ current_resource.task.stop
+ end
+ end
+ else
+ logger.warn "#{new_resource} task does not exist - nothing to do"
+ end
+ end
+
+ action :enable do
+ if current_resource.exists
+ logger.trace "#{new_resource} task exists"
+ if current_resource.task.status == "not scheduled"
+ converge_by("#{new_resource} task enabled") do
+ # TODO wind32-taskscheduler currently not having any method to handle this so using schtasks.exe here
+ run_schtasks "CHANGE", "ENABLE" => ""
+ end
+ else
+ logger.trace "#{new_resource} already enabled - nothing to do"
+ end
+ else
+ logger.fatal "#{new_resource} task does not exist - nothing to do"
+ raise Errno::ENOENT, "#{new_resource}: task does not exist, cannot enable"
+ end
+ end
+
+ action :disable do
+ if current_resource.exists
+ logger.info "#{new_resource} task exists"
+ if %w{ready running}.include?(current_resource.task.status)
+ converge_by("#{new_resource} task disabled") do
+ # TODO: in win32-taskscheduler there is no method which disables the task so currently calling disable with schtasks.exe
+ run_schtasks "CHANGE", "DISABLE" => ""
+ end
+ else
+ logger.warn "#{new_resource} already disabled - nothing to do"
+ end
+ else
+ logger.warn "#{new_resource} task does not exist - nothing to do"
+ end
+ end
+
+ action_class do
+ alias_method :action_change, :action_create
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_uac.rb b/lib/chef/resource/windows_uac.rb
new file mode 100644
index 0000000000..db5d5fd173
--- /dev/null
+++ b/lib/chef/resource/windows_uac.rb
@@ -0,0 +1,114 @@
+#
+# Author:: Tim Smith (<tsmith@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class WindowsUac < Chef::Resource
+ unified_mode true
+
+ provides :windows_uac
+
+ description 'The *windows_uac* resource configures UAC on Windows hosts by setting registry keys at `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System`'
+ introduced "15.0"
+ examples <<~DOC
+ **Disable UAC prompts for the admin**:
+
+ ``` ruby
+ windows_uac 'Disable UAC prompts for the admin' do
+ enable_uac true
+ prompt_on_secure_desktop false
+ consent_behavior_admins :no_prompt
+ end
+ ```
+
+ **Disable UAC entirely**:
+
+ ``` ruby
+ windows_uac 'Disable UAC entirely' do
+ enable_uac false
+ end
+ ```
+ DOC
+
+ # https://docs.microsoft.com/en-us/windows/security/identity-protection/user-account-control/user-account-control-group-policy-and-registry-key-settings#user-account-control-virtualize-file-and-registry-write-failures-to-per-user-locations
+ property :enable_uac, [TrueClass, FalseClass],
+ description: 'Enable or disable UAC Admin Approval Mode. If this is changed a system restart is required. Sets HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\EnableLUA.',
+ default: true # EnableLUA
+
+ property :require_signed_binaries, [TrueClass, FalseClass],
+ description: 'Only elevate executables that are signed and validated. Sets HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\EnableLUA\ValidateAdminCodeSignatures.',
+ default: false
+
+ property :prompt_on_secure_desktop, [TrueClass, FalseClass],
+ description: 'Switch to the secure desktop when prompting for elevation. Sets HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\EnableLUA\PromptOnSecureDesktop.',
+ default: true
+
+ property :detect_installers, [TrueClass, FalseClass],
+ description: 'Detect application installations and prompt for elevation. Sets HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\EnableLUA\EnableInstallerDetection.'
+
+ property :consent_behavior_admins, Symbol,
+ description: 'Behavior of the elevation prompt for administrators in Admin Approval Mode. Sets HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\EnableLUA\ConsentPromptBehaviorAdmin.',
+ equal_to: %i{no_prompt secure_prompt_for_creds secure_prompt_for_consent prompt_for_creds prompt_for_consent prompt_for_consent_non_windows_binaries},
+ default: :prompt_for_consent_non_windows_binaries
+
+ property :consent_behavior_users, Symbol,
+ description: 'Behavior of the elevation prompt for standard users. Sets HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\EnableLUA\ConsentPromptBehaviorUser.',
+ equal_to: %i{auto_deny secure_prompt_for_creds prompt_for_creds},
+ default: :prompt_for_creds
+
+ action :configure do
+ description 'Configures UAC by setting registry keys at \'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\''
+
+ registry_key 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' do
+ values [{ name: "EnableLUA", type: :dword, data: bool_to_reg(new_resource.enable_uac) },
+ { name: "ValidateAdminCodeSignatures", type: :dword, data: bool_to_reg(new_resource.require_signed_binaries) },
+ { name: "PromptOnSecureDesktop", type: :dword, data: bool_to_reg(new_resource.prompt_on_secure_desktop) },
+ { name: "ConsentPromptBehaviorAdmin", type: :dword, data: consent_behavior_admins_symbol_to_reg(new_resource.consent_behavior_admins) },
+ { name: "ConsentPromptBehaviorUser", type: :dword, data: consent_behavior_users_symbol_to_reg(new_resource.consent_behavior_users) },
+ { name: "EnableInstallerDetection", type: :dword, data: bool_to_reg(new_resource.detect_installers) },
+ ]
+ action :create
+ end
+ end
+
+ action_class do
+ # converts a Ruby true/false to a 1 or 0
+ #
+ # @return [Integer] 1:true, 0: false
+ def bool_to_reg(bool)
+ bool ? 1 : 0
+ end
+
+ # converts the symbols we use in the consent_behavior_admins property into numbers 0-5 based on their array index
+ #
+ # @return [Integer]
+ def consent_behavior_admins_symbol_to_reg(sym)
+ %i{no_prompt secure_prompt_for_creds secure_prompt_for_consent prompt_for_creds prompt_for_consent prompt_for_consent_non_windows_binaries}.index(sym)
+ end
+
+ # converts the symbols we use in the consent_behavior_users property into numbers 0-2 based on their array index
+ #
+ # @return [Integer]
+ def consent_behavior_users_symbol_to_reg(sym)
+ %i{auto_deny secure_prompt_for_creds prompt_for_creds}.index(sym)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_user_privilege.rb b/lib/chef/resource/windows_user_privilege.rb
new file mode 100644
index 0000000000..971338303d
--- /dev/null
+++ b/lib/chef/resource/windows_user_privilege.rb
@@ -0,0 +1,223 @@
+#
+# Author:: Jared Kauppila (<jared@kauppi.la>)
+# Author:: Vasundhara Jagdale(<vasundhara.jagdale@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class WindowsUserPrivilege < Chef::Resource
+ unified_mode true
+
+ provides :windows_user_privilege
+ description "The windows_user_privilege resource allows to add and set principal (User/Group) to the specified privilege.\n Ref: https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/user-rights-assignment"
+
+ introduced "16.0"
+
+ examples <<~DOC
+ **Set the SeNetworkLogonRight Privilege for the Builtin Administrators Group and Authenticated Users**:
+
+ ```ruby
+ windows_user_privilege 'Network Logon Rights' do
+ privilege 'SeNetworkLogonRight'
+ users ['BUILTIN\\Administrators', 'NT AUTHORITY\\Authenticated Users']
+ action :set
+ end
+ ```
+
+ **Add the SeDenyRemoteInteractiveLogonRight Privilege to the Builtin Guests and Local Accounts User Groups**:
+
+ ```ruby
+ windows_user_privilege 'Remote interactive logon' do
+ privilege 'SeDenyRemoteInteractiveLogonRight'
+ users ['Builtin\\Guests', 'NT AUTHORITY\\Local Account']
+ action :add
+ end
+ ```
+
+ **Provide only the Builtin Guests and Administrator Groups with the SeCreatePageFile Privilege**:
+
+ ```ruby
+ windows_user_privilege 'Create Pagefile' do
+ privilege 'SeCreatePagefilePrivilege'
+ users ['BUILTIN\\Guests', 'BUILTIN\\Administrators']
+ action :set
+ end
+ ```
+
+ **Remove the SeCreatePageFile Privilege from the Builtin Guests Group**:
+
+ ```ruby
+ windows_user_privilege 'Create Pagefile' do
+ privilege 'SeCreatePagefilePrivilege'
+ users ['BUILTIN\\Guests']
+ action :remove
+ end
+ ```
+
+ **Clear all users from the SeDenyNetworkLogonRight Privilege**:
+
+ ```ruby
+ windows_user_privilege 'Allow any user the Network Logon right' do
+ privilege 'SeDenyNetworkLogonRight'
+ action :clear
+ end
+ ```
+ DOC
+
+ PRIVILEGE_OPTS = %w{ SeAssignPrimaryTokenPrivilege
+ SeAuditPrivilege
+ SeBackupPrivilege
+ SeBatchLogonRight
+ SeChangeNotifyPrivilege
+ SeCreateGlobalPrivilege
+ SeCreatePagefilePrivilege
+ SeCreatePermanentPrivilege
+ SeCreateSymbolicLinkPrivilege
+ SeCreateTokenPrivilege
+ SeDebugPrivilege
+ SeDenyBatchLogonRight
+ SeDenyInteractiveLogonRight
+ SeDenyNetworkLogonRight
+ SeDenyRemoteInteractiveLogonRight
+ SeDenyServiceLogonRight
+ SeEnableDelegationPrivilege
+ SeImpersonatePrivilege
+ SeIncreaseBasePriorityPrivilege
+ SeIncreaseQuotaPrivilege
+ SeIncreaseWorkingSetPrivilege
+ SeInteractiveLogonRight
+ SeLoadDriverPrivilege
+ SeLockMemoryPrivilege
+ SeMachineAccountPrivilege
+ SeManageVolumePrivilege
+ SeNetworkLogonRight
+ SeProfileSingleProcessPrivilege
+ SeRelabelPrivilege
+ SeRemoteInteractiveLogonRight
+ SeRemoteShutdownPrivilege
+ SeRestorePrivilege
+ SeSecurityPrivilege
+ SeServiceLogonRight
+ SeShutdownPrivilege
+ SeSyncAgentPrivilege
+ SeSystemEnvironmentPrivilege
+ SeSystemProfilePrivilege
+ SeSystemtimePrivilege
+ SeTakeOwnershipPrivilege
+ SeTcbPrivilege
+ SeTimeZonePrivilege
+ SeTrustedCredManAccessPrivilege
+ SeUndockPrivilege
+ }.freeze
+
+ property :principal, String,
+ description: "An optional property to add the user to the given privilege. Use only with add and remove action.",
+ name_property: true
+
+ property :users, [Array, String],
+ description: "An optional property to set the privilege for given users. Use only with set action.",
+ coerce: proc { |v| Array(v) }
+
+ property :privilege, [Array, String],
+ description: "One or more privileges to set for users.",
+ required: true,
+ coerce: proc { |v| Array(v) },
+ callbacks: {
+ "Privilege property restricted to the following values: #{PRIVILEGE_OPTS}" => lambda { |n| (n - PRIVILEGE_OPTS).empty? },
+ }
+
+ load_current_value do |new_resource|
+ if new_resource.principal && (new_resource.action.include?(:add) || new_resource.action.include?(:remove))
+ privilege Chef::ReservedNames::Win32::Security.get_account_right(new_resource.principal)
+ end
+ end
+
+ action :add do
+ ([*new_resource.privilege] - [*current_resource.privilege]).each do |user_right|
+ converge_by("adding user '#{new_resource.principal}' privilege #{user_right}") do
+ Chef::ReservedNames::Win32::Security.add_account_right(new_resource.principal, user_right)
+ end
+ end
+ end
+
+ action :set do
+ if new_resource.users.nil? || new_resource.users.empty?
+ raise Chef::Exceptions::ValidationFailed, "Users are required property with set action."
+ end
+
+ users = []
+
+ # Getting users with its domain for comparison
+ new_resource.users.each do |user|
+ user = Chef::ReservedNames::Win32::Security.lookup_account_name(user)
+ users << user[1].account_name if user
+ end
+
+ new_resource.privilege.each do |privilege|
+ accounts = Chef::ReservedNames::Win32::Security.get_account_with_user_rights(privilege)
+
+ # comparing the existing accounts for privilege with users
+ unless users == accounts
+ # Removing only accounts which is not matching with users in new_resource
+ (accounts - users).each do |account|
+ converge_by("removing user '#{account}' from privilege #{privilege}") do
+ Chef::ReservedNames::Win32::Security.remove_account_right(account, privilege)
+ end
+ end
+
+ # Adding only users which is not already exist
+ (users - accounts).each do |user|
+ converge_by("adding user '#{user}' to privilege #{privilege}") do
+ Chef::ReservedNames::Win32::Security.add_account_right(user, privilege)
+ end
+ end
+ end
+ end
+ end
+
+ action :clear do
+ new_resource.privilege.each do |privilege|
+ accounts = Chef::ReservedNames::Win32::Security.get_account_with_user_rights(privilege)
+
+ # comparing the existing accounts for privilege with users
+ # Removing only accounts which is not matching with users in new_resource
+ accounts.each do |account|
+ converge_by("removing user '#{account}' from privilege #{privilege}") do
+ Chef::ReservedNames::Win32::Security.remove_account_right(account, privilege)
+ end
+ end
+ end
+ end
+
+ action :remove do
+ curr_res_privilege = current_resource.privilege
+ missing_res_privileges = (new_resource.privilege - curr_res_privilege)
+
+ if missing_res_privileges
+ Chef::Log.info("User \'#{new_resource.principal}\' for Privilege: #{missing_res_privileges.join(", ")} not found. Nothing to remove.")
+ end
+
+ (new_resource.privilege - missing_res_privileges).each do |user_right|
+ converge_by("removing user #{new_resource.principal} from privilege #{user_right}") do
+ Chef::ReservedNames::Win32::Security.remove_account_right(new_resource.principal, user_right)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_workgroup.rb b/lib/chef/resource/windows_workgroup.rb
new file mode 100644
index 0000000000..3c49f7cb3e
--- /dev/null
+++ b/lib/chef/resource/windows_workgroup.rb
@@ -0,0 +1,130 @@
+#
+# Author:: Derek Groh (<derekgroh@github.io>)
+# Copyright:: 2018, Derek Groh
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../resource"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Resource
+ class WindowsWorkgroup < Chef::Resource
+ unified_mode true
+
+ provides :windows_workgroup
+
+ description "Use the **windows_workgroup** resource to join or change the workgroup of a Windows host."
+ introduced "14.5"
+ examples <<~DOC
+ **Join a workgroup**:
+
+ ``` ruby
+ windows_workgroup 'myworkgroup'
+ ```
+
+ **Join a workgroup using a specific user**:
+
+ ``` ruby
+ windows_workgroup 'myworkgroup' do
+ user 'Administrator'
+ password 'passw0rd'
+ end
+ ```
+ DOC
+
+ property :workgroup_name, String,
+ description: "An optional property to set the workgroup name if it differs from the resource block's name.",
+ validation_message: "The 'workgroup_name' property must not contain spaces.",
+ regex: /^\S*$/, # no spaces
+ name_property: true
+
+ property :user, String,
+ description: "The local administrator user to use to change the workgroup. Required if using the `password` property.",
+ desired_state: false
+
+ property :password, String,
+ description: "The password for the local administrator user. Required if using the `user` property.",
+ sensitive: true,
+ desired_state: false
+
+ property :reboot, Symbol,
+ equal_to: %i{never request_reboot reboot_now},
+ validation_message: "The reboot property accepts :immediate (reboot as soon as the resource completes), :delayed (reboot once the #{ChefUtils::Dist::Infra::PRODUCT} run completes), and :never (Don't reboot)",
+ description: "Controls the system reboot behavior post workgroup joining. Reboot immediately, after the #{ChefUtils::Dist::Infra::PRODUCT} run completes, or never. Note that a reboot is necessary for changes to take effect.",
+ coerce: proc { |x| clarify_reboot(x) },
+ default: :immediate, desired_state: false
+
+ # This resource historically took `:immediate` and `:delayed` as arguments to the reboot property but then
+ # tried to shove that straight to the `reboot` resource which objected strenuously. We need to convert these
+ # legacy actions into actual reboot actions
+ #
+ # @return [Symbol] chef reboot resource action
+ def clarify_reboot(reboot_action)
+ case reboot_action
+ when :immediate
+ :reboot_now
+ when :delayed
+ :request_reboot
+ else
+ reboot_action
+ end
+ end
+
+ # define this again so we can default it to true. Otherwise failures print the password
+ # FIXME: this should now be unnecessary with the password property itself marked sensitive?
+ property :sensitive, [TrueClass, FalseClass],
+ default: true, desired_state: false
+
+ action :join do
+ description "Update the workgroup."
+
+ unless workgroup_member?
+ converge_by("join workstation workgroup #{new_resource.workgroup_name}") do
+ ps_run = powershell_exec(join_command)
+ raise "Failed to join the workgroup #{new_resource.workgroup_name}: #{ps_run.errors}}" if ps_run.error?
+
+ unless new_resource.reboot == :never
+ reboot "Reboot to join workgroup #{new_resource.workgroup_name}" do
+ action new_resource.reboot
+ reason "Reboot to join workgroup #{new_resource.workgroup_name}"
+ end
+ end
+ end
+ end
+ end
+
+ action_class do
+ # return [String] the appropriate PS command to joint the workgroup
+ def join_command
+ cmd = ""
+ cmd << "$pswd = ConvertTo-SecureString \'#{new_resource.password}\' -AsPlainText -Force;" if new_resource.password
+ cmd << "$credential = New-Object System.Management.Automation.PSCredential (\"#{new_resource.user}\",$pswd);" if new_resource.password
+ cmd << "Add-Computer -WorkgroupName #{new_resource.workgroup_name}"
+ cmd << " -Credential $credential" if new_resource.password
+ cmd << " -Force"
+ cmd
+ end
+
+ # @return [Boolean] is the node a member of the workgroup specified in the resource
+ def workgroup_member?
+ node_workgroup = powershell_exec!("(Get-WmiObject -Class Win32_ComputerSystem).Workgroup")
+ raise "Failed to determine if system already a member of workgroup #{new_resource.workgroup_name}" if node_workgroup.error?
+
+ String(node_workgroup.result).downcase.strip == new_resource.workgroup_name.downcase
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/yum_package.rb b/lib/chef/resource/yum_package.rb
index 9d69897f5f..f7c4517c6d 100644
--- a/lib/chef/resource/yum_package.rb
+++ b/lib/chef/resource/yum_package.rb
@@ -1,6 +1,6 @@
#
# Author:: AJ Christensen (<aj@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,33 +16,148 @@
# limitations under the License.
#
-require "chef/resource/package"
-require "chef/provider/package/yum"
+require_relative "package"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Resource
class YumPackage < Chef::Resource::Package
- resource_name :yum_package
- provides :package, os: "linux", platform_family: %w{rhel fedora}
-
- # Install a specific arch
- property :arch, [ String, Array ]
- # the {} on the proc here is because rspec chokes if it's do...end
- property :flush_cache,
- Hash,
- default: { before: false, after: false },
- coerce: proc { |v|
- if v.is_a?(Array)
- v.each_with_object({}) { |arg, obj| obj[arg] = true }
- elsif v.any?
- v
- else
- { before: v, after: v }
- end
- }
- property :allow_downgrade, [ true, false ], default: false
- property :yum_binary, String
+ unified_mode true
+ provides :yum_package
+ provides :package, platform_family: "fedora_derived"
+
+ description "Use the **yum_package** resource to install, upgrade, and remove packages with Yum"\
+ " for the Red Hat and CentOS platforms. The yum_package resource is able to resolve"\
+ " `provides` data for packages much like Yum can do when it is run from the command line."\
+ " This allows a variety of options for installing packages, like minimum versions,"\
+ " virtual provides, and library names."
+ examples <<~DOC
+ **Install an exact version**:
+
+ ``` ruby
+ yum_package 'netpbm = 10.35.58-8.el8'
+ ```
+
+ **Install a minimum version**:
+
+ ``` ruby
+ yum_package 'netpbm >= 10.35.58-8.el8'
+ ```
+
+ **Install a minimum version using the default action**:
+
+ ``` ruby
+ yum_package 'netpbm'
+ ```
+
+ **Install a version without worrying about the exact release**:
+
+ ``` ruby
+ yum_package 'netpbm-10.35*'
+ ```
+
+
+ **To install a package**:
+
+ ``` ruby
+ yum_package 'netpbm' do
+ action :install
+ end
+ ```
+
+ **To install a partial minimum version**:
+
+ ``` ruby
+ yum_package 'netpbm >= 10'
+ ```
+
+ **To install a specific architecture**:
+
+ ``` ruby
+ yum_package 'netpbm' do
+ arch 'i386'
+ end
+ ```
+
+ or:
+
+ ``` ruby
+ yum_package 'netpbm.x86_64'
+ ```
+
+ **To install a specific version-release**
+
+ ``` ruby
+ yum_package 'netpbm' do
+ version '10.35.58-8.el8'
+ end
+ ```
+
+ **Handle cookbook_file and yum_package resources in the same recipe**:
+
+ When a **cookbook_file** resource and a **yum_package** resource are
+ both called from within the same recipe, use the `flush_cache` attribute
+ to dump the in-memory Yum cache, and then use the repository immediately
+ to ensure that the correct package is installed:
+
+ ``` ruby
+ cookbook_file '/etc/yum.repos.d/custom.repo' do
+ source 'custom'
+ mode '0755'
+ end
+
+ yum_package 'pkg-that-is-only-in-custom-repo' do
+ action :install
+ flush_cache [ :before ]
+ end
+ ```
+ DOC
+
+ # XXX: the coercions here are due to the provider promiscuously updating the properties on the
+ # new_resource which causes immutable modification exceptions when passed an immutable node array.
+ #
+ # <lecture>
+ # THIS is why updating the new_resource in a provider is so terrible, and is equivalent to methods scribbling over
+ # its own arguments as unintended side-effects (and why functional languages that don't allow modifications
+ # of variables eliminate entire classes of bugs).
+ # </lecture>
+ property :package_name, [ String, Array ],
+ description: "One of the following: the name of a package, the name of a package and its architecture, the name of a dependency.",
+ identity: true, coerce: proc { |x| x.is_a?(Array) ? x.to_a : x }
+
+ property :version, [ String, Array ],
+ description: "The version of a package to be installed or upgraded. This property is ignored when using the `:upgrade` action.",
+ coerce: proc { |x| x.is_a?(Array) ? x.to_a : x }
+
+ property :arch, [ String, Array ],
+ description: "The architecture of the package to be installed or upgraded. This value can also be passed as part of the package name.",
+ coerce: proc { |x| x.is_a?(Array) ? x.to_a : x }
+
+ property :flush_cache, Hash,
+ description: "Flush the in-memory cache before or after a Yum operation that installs, upgrades, or removes a package. Accepts a Hash in the form: { :before => true/false, :after => true/false } or an Array in the form [ :before, :after ].\nYum automatically synchronizes remote metadata to a local cache. The #{ChefUtils::Dist::Infra::CLIENT} creates a copy of the local cache, and then stores it in-memory during the #{ChefUtils::Dist::Infra::CLIENT} run. The in-memory cache allows packages to be installed during the #{ChefUtils::Dist::Infra::CLIENT} run without the need to continue synchronizing the remote metadata to the local cache while the #{ChefUtils::Dist::Infra::CLIENT} run is in-progress.",
+ default: { before: false, after: false },
+ coerce: proc { |v|
+ if v.is_a?(Hash)
+ v
+ elsif v.is_a?(Array)
+ v.each_with_object({}) { |arg, obj| obj[arg] = true }
+ elsif v.is_a?(TrueClass) || v.is_a?(FalseClass)
+ { before: v, after: v }
+ elsif v == :before
+ { before: true, after: false }
+ elsif v == :after
+ { after: true, before: false }
+ end
+ }
+
+ property :allow_downgrade, [ TrueClass, FalseClass ],
+ description: "Allow downgrading a package to satisfy requested version requirements.",
+ default: true,
+ desired_state: false
+
+ property :yum_binary, String,
+ description: "The path to the yum binary."
end
end
end
diff --git a/lib/chef/resource/yum_repository.rb b/lib/chef/resource/yum_repository.rb
index 3633f4421b..b5ad2688eb 100644
--- a/lib/chef/resource/yum_repository.rb
+++ b/lib/chef/resource/yum_repository.rb
@@ -1,6 +1,6 @@
#
# Author:: Thom May (<thom@chef.io>)
-# Copyright:: Copyright (c) 2016 Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,64 +16,195 @@
# limitations under the License.
#
-require "chef/resource"
+require_relative "../resource"
class Chef
class Resource
class YumRepository < Chef::Resource
- resource_name :yum_repository
- provides :yum_repository
-
- # http://linux.die.net/man/5/yum.conf
- property :baseurl, String, regex: /.*/
- property :cost, String, regex: /^\d+$/
- property :clean_headers, [TrueClass, FalseClass], default: false # deprecated
- property :clean_metadata, [TrueClass, FalseClass], default: true
- property :description, String, regex: /.*/, default: "Yum Repository"
- property :enabled, [TrueClass, FalseClass], default: true
- property :enablegroups, [TrueClass, FalseClass]
- property :exclude, String, regex: /.*/
- property :failovermethod, String, equal_to: %w{priority roundrobin}
- property :fastestmirror_enabled, [TrueClass, FalseClass]
- property :gpgcheck, [TrueClass, FalseClass]
- property :gpgkey, [String, Array], regex: /.*/
- property :http_caching, String, equal_to: %w{packages all none}
- property :include_config, String, regex: /.*/
- property :includepkgs, String, regex: /.*/
- property :keepalive, [TrueClass, FalseClass]
- property :make_cache, [TrueClass, FalseClass], default: true
- property :max_retries, [String, Integer]
- property :metadata_expire, String, regex: [/^\d+$/, /^\d+[mhd]$/, /never/]
- property :mirrorexpire, String, regex: /.*/
- property :mirrorlist, String, regex: /.*/
- property :mirror_expire, String, regex: [/^\d+$/, /^\d+[mhd]$/]
- property :mirrorlist_expire, String, regex: [/^\d+$/, /^\d+[mhd]$/]
- property :mode, default: "0644"
- property :priority, String, regex: /^(\d?[0-9]|[0-9][0-9])$/
- property :proxy, String, regex: /.*/
- property :proxy_username, String, regex: /.*/
- property :proxy_password, String, regex: /.*/
- property :username, String, regex: /.*/
- property :password, String, regex: /.*/
- property :repo_gpgcheck, [TrueClass, FalseClass]
- property :report_instanceid, [TrueClass, FalseClass]
- property :repositoryid, String, regex: /.*/, name_attribute: true
- property :sensitive, [TrueClass, FalseClass], default: false
- property :skip_if_unavailable, [TrueClass, FalseClass]
- property :source, String, regex: /.*/
- property :sslcacert, String, regex: /.*/
- property :sslclientcert, String, regex: /.*/
- property :sslclientkey, String, regex: /.*/
- property :sslverify, [TrueClass, FalseClass]
- property :timeout, String, regex: /^\d+$/
- property :options, Hash
+ unified_mode true
+
+ provides(:yum_repository) { true }
+
+ description "Use the **yum_repository** resource to manage a Yum repository configuration file located at `/etc/yum.repos.d/repositoryid.repo` on the local machine. This configuration file specifies which repositories to reference, how to handle cached data, etc."
+ introduced "12.14"
+ examples <<~DOC
+ **Add an internal company repository**:
+
+ ```ruby
+ yum_repository 'OurCo' do
+ description 'OurCo yum repository'
+ mirrorlist 'http://artifacts.ourco.org/mirrorlist?repo=ourco-8&arch=$basearch'
+ gpgkey 'http://artifacts.ourco.org/pub/yum/RPM-GPG-KEY-OURCO-8'
+ action :create
+ end
+ ```
+
+ **Delete a repository**:
+
+ ```ruby
+ yum_repository 'CentOS-Media' do
+ action :delete
+ end
+ ```
+ DOC
+
+ # http://linux.die.net/man/5/yum.conf as well as
+ # http://dnf.readthedocs.io/en/latest/conf_ref.html
+ property :reposdir, String,
+ description: "The directory where the Yum repository files should be stored",
+ default: "/etc/yum.repos.d/",
+ introduced: "16.9"
+
+ property :baseurl, [String, Array],
+ description: "URL to the directory where the Yum repository's `repodata` directory lives. Can be an `http://`, `https://` or a `ftp://` URLs. You can specify multiple URLs in one `baseurl` statement."
+
+ property :clean_headers, [TrueClass, FalseClass],
+ description: "Specifies whether you want to purge the package data files that are downloaded from a Yum repository and held in a cache directory.",
+ deprecated: true,
+ default: false
+
+ property :clean_metadata, [TrueClass, FalseClass],
+ description: "Specifies whether you want to purge all of the packages downloaded from a Yum repository and held in a cache directory.",
+ default: true
+
+ property :cost, String, regex: /^\d+$/,
+ description: "Relative cost of accessing this repository. Useful for weighing one repo's packages as greater/less than any other.",
+ validation_message: "The cost property must be a numeric value!"
+
+ property :description, String,
+ description: "Descriptive name for the repository channel and maps to the 'name' parameter in a repository .conf.",
+ default: "Yum Repository"
+
+ property :enabled, [TrueClass, FalseClass],
+ description: "Specifies whether or not Yum should use this repository.",
+ default: true
+
+ property :enablegroups, [TrueClass, FalseClass],
+ description: "Specifies whether Yum will allow the use of package groups for this repository."
+
+ property :exclude, String,
+ description: "List of packages to exclude from updates or installs. This should be a space separated list. Shell globs using wildcards (eg. * and ?) are allowed."
+
+ property :failovermethod, String,
+ description: "Method to determine how to switch to a new server if the current one fails, which can either be `roundrobin` or `priority`. `roundrobin` randomly selects a URL out of the list of URLs to start with and proceeds through each of them as it encounters a failure contacting the host. `priority` starts from the first `baseurl` listed and reads through them sequentially.",
+ equal_to: %w{priority roundrobin}
+
+ property :fastestmirror_enabled, [TrueClass, FalseClass],
+ description: "Specifies whether to use the fastest mirror from a repository configuration when more than one mirror is listed in that configuration."
+
+ property :gpgcheck, [TrueClass, FalseClass],
+ description: "Specifies whether or not Yum should perform a GPG signature check on the packages received from a repository.",
+ default: true
+
+ property :gpgkey, [String, Array],
+ description: "URL pointing to the ASCII-armored GPG key file for the repository. This is used if Yum needs a public key to verify a package and the required key hasn't been imported into the RPM database. If this option is set, Yum will automatically import the key from the specified URL. Multiple URLs may be specified in the same manner as the baseurl option. If a GPG key is required to install a package from a repository, all keys specified for that repository will be installed.\nMultiple URLs may be specified in the same manner as the baseurl option. If a GPG key is required to install a package from a repository, all keys specified for that repository will be installed."
+
+ property :http_caching, String, equal_to: %w{packages all none},
+ description: "Determines how upstream HTTP caches are instructed to handle any HTTP downloads that Yum does. This option can take the following values:\n - `all` means all HTTP downloads should be cached\n - `packages` means only RPM package downloads should be cached, but not repository metadata downloads\n - `none` means no HTTP downloads should be cached.\n\nThe default value of `all` is recommended unless you are experiencing caching related issues."
+
+ property :include_config, String,
+ description: "An external configuration file using the format `url://to/some/location`."
+
+ property :includepkgs, String,
+ description: "Inverse of exclude property. This is a list of packages you want to use from a repository. If this option lists only one package then that is all Yum will ever see from the repository."
+
+ property :keepalive, [TrueClass, FalseClass],
+ description: "Determines whether or not HTTP/1.1 `keep-alive` should be used with this repository."
+
+ property :make_cache, [TrueClass, FalseClass],
+ description: "Determines whether package files downloaded by Yum stay in cache directories. By using cached data, you can carry out certain operations without a network connection.",
+ default: true
+
+ property :max_retries, [String, Integer],
+ description: "Number of times any attempt to retrieve a file should retry before returning an error. Setting this to `0` makes Yum try forever."
+
+ property :metadata_expire, String, regex: [/^\d+$/, /^\d+[mhd]$/, /never/],
+ description: "Time (in seconds) after which the metadata will expire. If the current metadata downloaded is less than the value specified, then Yum will not update the metadata against the repository. If you find that Yum is not downloading information on updates as often as you would like lower the value of this option. You can also change from the default of using seconds to using days, hours or minutes by appending a `d`, `h` or `m` respectively. The default is six hours to compliment yum-updates running once per hour. It is also possible to use the word `never`, meaning that the metadata will never expire. Note: When using a metalink file, the metalink must always be newer than the metadata for the repository due to the validation, so this timeout also applies to the metalink file.",
+ validation_message: "The metadata_expire property must be a numeric value for time in seconds, the string 'never', or a numeric value appended with with `d`, `h`, or `m`!"
+
+ property :metalink, String,
+ description: "Specifies a URL to a metalink file for the repomd.xml, a list of mirrors for the entire repository are generated by converting the mirrors for the repomd.xml file to a baseurl."
+
+ property :mirror_expire, String, regex: [/^\d+$/, /^\d+[mhd]$/],
+ description: "Time (in seconds) after which the mirrorlist locally cached will expire. If the current mirrorlist is less than this many seconds old then Yum will not download another copy of the mirrorlist, it has the same extra format as metadata_expire. If you find that Yum is not downloading the mirrorlists as often as you would like lower the value of this option. You can also change from the default of using seconds to using days, hours or minutes by appending a `d`, `h` or `m` respectively.",
+ validation_message: "The mirror_expire property must be a numeric value for time in seconds, the string 'never', or a numeric value appended with with `d`, `h`, or `m`!"
+
+ property :mirrorlist_expire, String, regex: [/^\d+$/, /^\d+[mhd]$/],
+ description: "Specifies the time (in seconds) after which the mirrorlist locally cached will expire. If the current mirrorlist is less than the value specified, then Yum will not download another copy of the mirrorlist. You can also change from the default of using seconds to using days, hours or minutes by appending a `d`, `h` or `m` respectively.",
+ validation_message: "The mirrorlist_expire property must be a numeric value for time in seconds, the string 'never', or a numeric value appended with with `d`, `h`, or `m`!"
+
+ property :mirrorlist, String,
+ description: "URL to a file containing a list of baseurls. This can be used instead of or with the baseurl option. Substitution variables, described below, can be used with this option."
+
+ property :mode, [String, Integer],
+ description: "Permissions mode of .repo file on disk. This is useful for scenarios where secrets are in the repo file. If this value is set to `600`, normal users will not be able to use Yum search, Yum info, etc.",
+ default: "0644"
+
+ property :options, Hash,
+ description: "Specifies the repository options."
+
+ property :password, String,
+ description: "Password to use with the username for basic authentication."
+
+ property :priority, String, regex: /^(\d?[1-9]|[0-9][0-9])$/,
+ description: "Assigns a priority to a repository where the priority value is between `1` and `99` inclusive. Priorities are used to enforce ordered protection of repositories. Packages from repositories with a lower priority (higher numerical value) will never be used to upgrade packages that were installed from a repository with a higher priority (lower numerical value). The repositories with the lowest numerical priority number have the highest priority.",
+ validation_message: "The priority property must be a numeric value from 1-99!"
+
+ property :proxy_password, String,
+ description: "Password for this proxy."
+
+ property :proxy_username, String,
+ description: "Username to use for proxy."
+
+ property :proxy, String,
+ description: "URL to the proxy server that Yum should use."
+
+ property :repo_gpgcheck, [TrueClass, FalseClass],
+ description: "Determines whether or not Yum should perform a GPG signature check on the repodata from this repository."
+
+ property :report_instanceid, [TrueClass, FalseClass],
+ description: "Determines whether to report the instance ID when using Amazon Linux AMIs and repositories."
+
+ property :repositoryid, String, regex: [%r{^[^/]+$}],
+ description: "An optional property to set the repository name if it differs from the resource block's name.",
+ validation_message: "repositoryid property cannot contain a forward slash '/'",
+ name_property: true
+
+ property :skip_if_unavailable, [TrueClass, FalseClass],
+ description: "Allow yum to continue if this repository cannot be contacted for any reason."
+
+ property :source, String,
+ description: "Use a custom template source instead of the default one."
+
+ property :sslcacert, String,
+ description: "Path to the directory containing the databases of the certificate authorities Yum should use to verify SSL certificates."
+
+ property :sslclientcert, String,
+ description: "Path to the SSL client certificate Yum should use to connect to repos/remote sites."
+
+ property :sslclientkey, String,
+ description: "Path to the SSL client key Yum should use to connect to repos/remote sites."
+
+ property :sslverify, [TrueClass, FalseClass],
+ description: "Determines whether Yum will verify SSL certificates/hosts."
+
+ property :throttle, [String, Integer],
+ description: "Enable bandwidth throttling for downloads."
+
+ property :timeout, String, regex: /^\d+$/,
+ description: "Number of seconds to wait for a connection before timing out. Defaults to 30 seconds. This may be too short of a time for extremely overloaded sites.",
+ validation_message: "The timeout property must be a numeric value!"
+
+ property :username, String,
+ description: "Username to use for basic authentication to a repository."
default_action :create
- allowed_actions :create, :remove, :make_cache, :add, :delete
+ allowed_actions :create, :remove, :makecache, :add, :delete
# provide compatibility with the yum cookbook < 3.0 properties
alias_method :url, :baseurl
alias_method :keyurl, :gpgkey
+ alias_method :mirrorexpire, :mirror_expire
end
end
end
diff --git a/lib/chef/resource/zypper_package.rb b/lib/chef/resource/zypper_package.rb
index f9e3eef49e..5901090abd 100644
--- a/lib/chef/resource/zypper_package.rb
+++ b/lib/chef/resource/zypper_package.rb
@@ -16,13 +16,58 @@
# limitations under the License.
#
-require "chef/resource/package"
+require_relative "package"
class Chef
class Resource
class ZypperPackage < Chef::Resource::Package
- resource_name :zypper_package
+ unified_mode true
+
+ provides :zypper_package
provides :package, platform_family: "suse"
+
+ description "Use the **zypper_package** resource to install, upgrade, and remove packages with Zypper for the SUSE Enterprise and openSUSE platforms."
+ examples <<~DOC
+ **Install a package using package manager:**
+
+ ``` ruby
+ zypper_package 'name of package' do
+ action :install
+ end
+ ```
+
+ **Install a package using local file:**
+
+ ``` ruby
+ zypper_package 'jwhois' do
+ action :install
+ source '/path/to/jwhois.rpm'
+ end
+ ```
+
+ **Install without using recommend packages as a dependency:**
+
+ ``` ruby
+ package 'apache2' do
+ options '--no-recommends'
+ end
+ ```
+ DOC
+
+ property :gpg_check, [ TrueClass, FalseClass ],
+ description: "Verify the package's GPG signature. Can also be controlled site-wide using the `zypper_check_gpg` config option.",
+ default: lazy { Chef::Config[:zypper_check_gpg] }, default_description: "true"
+
+ property :allow_downgrade, [ TrueClass, FalseClass ],
+ description: "Allow downgrading a package to satisfy requested version requirements.",
+ default: true,
+ desired_state: false,
+ introduced: "13.6"
+
+ property :global_options, [ String, Array ],
+ description: "One (or more) additional command options that are passed to the command. For example, common zypper directives, such as `--no-recommends`. See the [zypper man page](https://en.opensuse.org/SDB:Zypper_manual_(plain)) for the full list.",
+ coerce: proc { |x| x.is_a?(String) ? x.shellsplit : x },
+ introduced: "14.6"
end
end
end
diff --git a/lib/chef/resource/zypper_repository.rb b/lib/chef/resource/zypper_repository.rb
new file mode 100644
index 0000000000..05856cc9bc
--- /dev/null
+++ b/lib/chef/resource/zypper_repository.rb
@@ -0,0 +1,116 @@
+#
+# Author:: Tim Smith (<tsmith@chef.io>)
+# Copyright:: Copyright (c) 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_relative "../resource"
+
+class Chef
+ class Resource
+ class ZypperRepository < Chef::Resource
+ unified_mode true
+
+ provides(:zypper_repository) { true }
+ provides(:zypper_repo) { true }
+
+ description "Use the **zypper_repository** resource to create Zypper package repositories on SUSE Enterprise Linux and openSUSE systems. This resource maintains full compatibility with the **zypper_repository** resource in the existing **zypper** cookbook."
+ introduced "13.3"
+ examples <<~DOC
+ **Add the Apache repo on openSUSE Leap 15**:
+
+ ``` ruby
+ zypper_repository 'apache' do
+ baseurl 'http://download.opensuse.org/repositories/Apache'
+ path '/openSUSE_Leap_15.0'
+ type 'rpm-md'
+ priority '100'
+ end
+ ```
+ DOC
+
+ property :repo_name, String,
+ regex: [%r{^[^/]+$}],
+ description: "An optional property to set the repository name if it differs from the resource block's name.",
+ validation_message: "repo_name property cannot contain a forward slash `/`",
+ name_property: true
+
+ property :description, String,
+ description: "The description of the repository that will be shown by the `zypper repos` command."
+
+ property :type, String,
+ description: "Specifies the repository type.",
+ default: "NONE"
+
+ property :enabled, [TrueClass, FalseClass],
+ description: "Determines whether or not the repository should be enabled.",
+ default: true
+
+ property :autorefresh, [TrueClass, FalseClass],
+ description: "Determines whether or not the repository should be refreshed automatically.",
+ default: true
+
+ property :gpgcheck, [TrueClass, FalseClass],
+ description: "Determines whether or not to perform a GPG signature check on the repository.",
+ default: true
+
+ property :gpgkey, String,
+ description: "The location of the repository key to be imported."
+
+ property :baseurl, String,
+ description: "The base URL for the Zypper repository, such as `http://download.opensuse.org`."
+
+ property :mirrorlist, String,
+ description: "The URL of the mirror list that will be used."
+
+ property :path, String,
+ description: "The relative path from the repository's base URL."
+
+ property :priority, Integer,
+ description: "Determines the priority of the Zypper repository.",
+ default: 99
+
+ property :keeppackages, [TrueClass, FalseClass],
+ description: "Determines whether or not packages should be saved.",
+ default: false
+
+ property :mode, [String, Integer],
+ description: "The file mode of the repository file.",
+ default: "0644"
+
+ property :refresh_cache, [TrueClass, FalseClass],
+ description: "Determines whether or not the package cache should be refreshed.",
+ default: true
+
+ property :source, String,
+ description: "The name of the template for the repository file. Only necessary if you're not using the built in template."
+
+ property :cookbook, String,
+ description: "The cookbook to source the repository template file from. Only necessary if you're not using the built in template.",
+ desired_state: false
+
+ property :gpgautoimportkeys, [TrueClass, FalseClass],
+ description: "Automatically import the specified key when setting up the repository.",
+ default: true
+
+ default_action :create
+ allowed_actions :create, :remove, :add, :refresh
+
+ # provide compatibility with the zypper cookbook
+ alias_method :key, :gpgkey
+ alias_method :uri, :baseurl
+ end
+ end
+end
diff --git a/lib/chef/resource_builder.rb b/lib/chef/resource_builder.rb
index 57c57dd8c3..9f2cd657f9 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 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -43,23 +43,14 @@ class Chef
end
def build(&block)
- 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
- # If we have a resource like this one, we want to steal its state
- # This behavior is very counter-intuitive and should be removed.
- # See CHEF-3694, https://tickets.opscode.com/browse/CHEF-3694
- # Moved to this location to resolve CHEF-5052, https://tickets.opscode.com/browse/CHEF-5052
- if prior_resource
- resource.load_from(prior_resource)
- end
-
resource.cookbook_name = cookbook_name
resource.recipe_name = recipe_name
# Determine whether this resource is being created in the context of an enclosing Provider
@@ -79,18 +70,17 @@ class Chef
end
end
- # emit a cloned resource warning if it is warranted
- if prior_resource
- if is_trivial_resource?(prior_resource) && identicalish_resources?(prior_resource, resource)
- emit_harmless_cloning_debug
- else
- emit_cloned_resource_warning
- end
- end
-
# Run optional resource hook
resource.after_created
+ # Force to compile_time execution if the flag is set
+ if resource.compile_time
+ Array(resource.action).each do |action|
+ resource.run_action(action)
+ end
+ resource.action :nothing
+ end
+
resource
end
@@ -103,54 +93,9 @@ class Chef
@resource_class ||= Chef::Resource.resource_for_node(type, run_context.node)
end
- def is_trivial_resource?(resource)
- trivial_resource = resource_class.new(name, run_context)
- # force un-lazy the name property on the created trivial resource
- name_property = resource_class.properties.find { |sym, p| p.name_property? }
- trivial_resource.send(name_property[0]) unless name_property.nil?
- identicalish_resources?(trivial_resource, resource)
- end
-
- # this is an equality test specific to checking for 3694 cloning warnings
- def identicalish_resources?(first, second)
- skipped_ivars = [ :@source_line, :@cookbook_name, :@recipe_name, :@params, :@elapsed_time, :@declared_type ]
- checked_ivars = ( first.instance_variables | second.instance_variables ) - skipped_ivars
- non_matching_ivars = checked_ivars.reject do |iv|
- if iv == :@action && ( [first.instance_variable_get(iv)].flatten == [:nothing] || [second.instance_variable_get(iv)].flatten == [:nothing] )
- # :nothing action on either side of the comparison always matches
- true
- else
- first.instance_variable_get(iv) == second.instance_variable_get(iv)
- end
- end
- Chef::Log.debug("ivars which did not match with the prior resource: #{non_matching_ivars}")
- non_matching_ivars.empty?
- end
-
- def emit_cloned_resource_warning
- message = "Cloning resource attributes for #{resource} from prior resource (CHEF-3694)"
- message << "\nPrevious #{prior_resource}: #{prior_resource.source_line}" if prior_resource.source_line
- message << "\nCurrent #{resource}: #{resource.source_line}" if resource.source_line
- Chef.log_deprecation(message)
- end
-
- def emit_harmless_cloning_debug
- Chef::Log.debug("Harmless resource cloning from #{prior_resource}:#{prior_resource.source_line} to #{resource}:#{resource.source_line}")
- end
-
- def prior_resource
- @prior_resource ||=
- begin
- key = "#{type}[#{name}]"
- run_context.resource_collection.lookup_local(key)
- rescue Chef::Exceptions::ResourceNotFound
- nil
- end
- end
-
end
end
-require "chef/exceptions"
-require "chef/resource"
-require "chef/log"
+require_relative "exceptions"
+require_relative "resource"
+require_relative "log"
diff --git a/lib/chef/resource_collection.rb b/lib/chef/resource_collection.rb
index 8eaa2961c4..588fe12f0a 100644
--- a/lib/chef/resource_collection.rb
+++ b/lib/chef/resource_collection.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -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_relative "resource_collection/resource_set"
+require_relative "resource_collection/resource_list"
+require_relative "resource_collection/resource_collection_serialization"
+require_relative "log"
+require "forwardable" unless defined?(Forwardable)
##
# ResourceCollection currently handles two tasks:
@@ -32,6 +32,8 @@ class Chef
include ResourceCollectionSerialization
extend Forwardable
+ attr_accessor :unified_mode
+
attr_reader :resource_set, :resource_list
attr_accessor :run_context
@@ -41,11 +43,12 @@ class Chef
@run_context = run_context
@resource_set = ResourceSet.new
@resource_list = ResourceList.new
+ @unified_mode = false
end
# @param resource [Chef::Resource] The resource to insert
# @param resource_type [String,Symbol] If known, the resource type used in the recipe, Eg `package`, `execute`
- # @param instance_name [String] If known, the recource name as used in the recipe, IE `vim` in `package 'vim'`
+ # @param instance_name [String] If known, the resource name as used in the recipe, IE `vim` in `package 'vim'`
# This method is meant to be the 1 insert method necessary in the future. It should support all known use cases
# for writing into the ResourceCollection.
def insert(resource, opts = {})
@@ -57,11 +60,15 @@ class Chef
else
resource_set.insert_as(resource)
end
+ if unified_mode
+ run_context.runner.run_all_actions(resource)
+ end
end
def delete(key)
- resource_list.delete(key)
- resource_set.delete(key)
+ res = resource_set.delete(key)
+ resource_list.delete(res.to_s)
+ res
end
# @deprecated
@@ -86,9 +93,9 @@ class Chef
# Read-only methods are simple to delegate - doing that below
resource_list_methods = Enumerable.instance_methods +
- [:iterator, :all_resources, :[], :each, :execute_each_resource, :each_index, :empty?] -
+ %i{iterator all_resources [] each execute_each_resource each_index empty?} -
[:find] # find overridden below
- resource_set_methods = [:resources, :keys, :validate_lookup_spec!]
+ resource_set_methods = %i{resources keys validate_lookup_spec!}
def_delegators :resource_list, *resource_list_methods
def_delegators :resource_set, *resource_set_methods
@@ -117,12 +124,23 @@ class Chef
end
end
+ def self.from_hash(o)
+ collection = new
+ { "@resource_list" => "ResourceList", "@resource_set" => "ResourceSet" }.each_pair do |name, klass|
+ obj = Chef::ResourceCollection.const_get(klass).from_hash(o["instance_vars"].delete(name))
+ collection.instance_variable_set(name.to_sym, obj)
+ end
+ collection.instance_variable_set(:@run_context, o["instance_vars"].delete("@run_context"))
+ collection
+ end
+
private
def lookup_recursive(rc, key)
rc.resource_collection.resource_set.lookup(key)
rescue Chef::Exceptions::ResourceNotFound
raise if rc.parent_run_context.nil?
+
lookup_recursive(rc.parent_run_context, key)
end
@@ -130,6 +148,7 @@ class Chef
rc.resource_collection.resource_set.find(*args)
rescue Chef::Exceptions::ResourceNotFound
raise if rc.parent_run_context.nil?
+
find_recursive(rc.parent_run_context, *args)
end
end
diff --git a/lib/chef/resource_collection/resource_collection_serialization.rb b/lib/chef/resource_collection/resource_collection_serialization.rb
index 0e76296a4a..524f2a6c54 100644
--- a/lib/chef/resource_collection/resource_collection_serialization.rb
+++ b/lib/chef/resource_collection/resource_collection_serialization.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Ball (<tball@chef.io>)
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,14 +15,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+
+require_relative "../json_compat"
+
class Chef
class ResourceCollection
module ResourceCollectionSerialization
# Serialize this object as a hash
- def to_hash
- instance_vars = Hash.new
- self.instance_variables.each do |iv|
- instance_vars[iv] = self.instance_variable_get(iv)
+ def to_h
+ instance_vars = {}
+ instance_variables.each do |iv|
+ instance_vars[iv] = instance_variable_get(iv)
end
{
"json_class" => self.class.name,
@@ -30,6 +33,8 @@ class Chef
}
end
+ alias_method :to_hash, :to_h
+
def to_json(*a)
Chef::JSONCompat.to_json(to_hash, *a)
end
@@ -39,19 +44,24 @@ class Chef
end
module ClassMethods
- def json_create(o)
- collection = self.new()
+ def from_hash(o)
+ collection = new
o["instance_vars"].each do |k, v|
collection.instance_variable_set(k.to_sym, v)
end
collection
end
+
+ def from_json(j)
+ from_hash(Chef::JSONCompat.parse(j))
+ end
end
def is_chef_resource!(arg)
- unless arg.kind_of?(Chef::Resource)
+ unless arg.is_a?(Chef::Resource)
raise ArgumentError, "Cannot insert a #{arg.class} into a resource collection: must be a subclass of Chef::Resource"
end
+
true
end
end
diff --git a/lib/chef/resource_collection/resource_list.rb b/lib/chef/resource_collection/resource_list.rb
index 9fe012d4c3..a290ecad62 100644
--- a/lib/chef/resource_collection/resource_list.rb
+++ b/lib/chef/resource_collection/resource_list.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Ball (<tball@chef.io>)
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -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_relative "../resource"
+require_relative "stepable_iterator"
+require_relative "resource_collection_serialization"
+require "forwardable" unless defined?(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.
@@ -36,11 +36,11 @@ class Chef
private :resources
# Delegate direct access methods to the @resources array
# 4 extra methods here are not included in the Enumerable's instance methods
- direct_access_methods = Enumerable.instance_methods + [ :[], :each, :each_index, :empty? ]
+ direct_access_methods = Enumerable.instance_methods + %i{[] each each_index empty?}
def_delegators :resources, *(direct_access_methods)
def initialize
- @resources = Array.new
+ @resources = []
@insert_after_idx = nil
end
@@ -69,11 +69,13 @@ class Chef
def delete(key)
raise ArgumentError, "Must pass a Chef::Resource or String to delete" unless key.is_a?(String) || key.is_a?(Chef::Resource)
+
key = key.to_s
ret = @resources.reject! { |r| r.to_s == key }
if ret.nil?
raise Chef::Exceptions::ResourceNotFound, "Cannot find a resource matching #{key} (did you define it first?)"
end
+
ret
end
@@ -95,6 +97,12 @@ class Chef
end
end
+ def self.from_hash(o)
+ collection = new
+ resources = o["instance_vars"]["@resources"].map { |r| Chef::Resource.from_hash(r) }
+ collection.instance_variable_set(:@resources, resources)
+ collection
+ end
end
end
end
diff --git a/lib/chef/resource_collection/resource_set.rb b/lib/chef/resource_collection/resource_set.rb
index 99be025cd5..26521010bd 100644
--- a/lib/chef/resource_collection/resource_set.rb
+++ b/lib/chef/resource_collection/resource_set.rb
@@ -1,6 +1,6 @@
#
# Author:: Tyler Ball (<tball@chef.io>)
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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/resource"
-require "chef/resource_collection/resource_collection_serialization"
+require_relative "../resource"
+require_relative "resource_collection_serialization"
class Chef
class ResourceCollection
@@ -26,14 +26,17 @@ class Chef
# Matches a multiple resource lookup specification,
# e.g., "service[nginx,unicorn]"
- MULTIPLE_RESOURCE_MATCH = /^(.+)\[(.+?),(.+)\]$/
+ MULTIPLE_RESOURCE_MATCH = /^(.+)\[(.+?),(.+)\]$/.freeze
# Matches a single resource lookup specification,
# e.g., "service[nginx]"
- SINGLE_RESOURCE_MATCH = /^(.+)\[(.+)\]$/
+ SINGLE_RESOURCE_MATCH = /^(.+)\[(.*)\]$/.freeze
+
+ # Matches e.g. "apt_update" with no name
+ NAMELESS_RESOURCE_MATCH = /^([^\[\]\s]+)$/.freeze
def initialize
- @resources_by_key = Hash.new
+ @resources_by_key = {}
end
def keys
@@ -50,22 +53,26 @@ class Chef
def lookup(key)
raise ArgumentError, "Must pass a Chef::Resource or String to lookup" unless key.is_a?(String) || key.is_a?(Chef::Resource)
+
key = key.to_s
res = @resources_by_key[key]
unless res
raise Chef::Exceptions::ResourceNotFound, "Cannot find a resource matching #{key} (did you define it first?)"
end
+
res
end
def delete(key)
raise ArgumentError, "Must pass a Chef::Resource or String to delete" unless key.is_a?(String) || key.is_a?(Chef::Resource)
+
key = key.to_s
res = @resources_by_key.delete(key)
if res == @resources_by_key.default
raise Chef::Exceptions::ResourceNotFound, "Cannot find a resource matching #{key} (did you define it first?)"
end
+
res
end
@@ -82,7 +89,7 @@ class Chef
# Raises an ArgumentError if you feed it bad lookup information
# Raises a Runtime Error if it can't find the resources you are looking for.
def find(*args)
- results = Array.new
+ results = []
args.each do |arg|
case arg
when Hash
@@ -116,22 +123,26 @@ class Chef
# * Chef::Exceptions::InvalidResourceSpecification for all invalid input.
def validate_lookup_spec!(query_object)
case query_object
- when Chef::Resource
- true
- when SINGLE_RESOURCE_MATCH, MULTIPLE_RESOURCE_MATCH
- true
- when Hash
+ when Chef::Resource, SINGLE_RESOURCE_MATCH, MULTIPLE_RESOURCE_MATCH, NAMELESS_RESOURCE_MATCH, Hash
true
when String
raise Chef::Exceptions::InvalidResourceSpecification,
- "The string `#{query_object}' is not valid for resource collection lookup. Correct syntax is `resource_type[resource_name]'"
+ "The string `#{query_object}' is not valid for resource collection lookup. Correct syntax is `resource_type[resource_name]'"
else
raise Chef::Exceptions::InvalidResourceSpecification,
- "The object `#{query_object.inspect}' is not valid for resource collection lookup. " +
- "Use a String like `resource_type[resource_name]' or a Chef::Resource object"
+ "The object `#{query_object.inspect}' is not valid for resource collection lookup. " +
+ "Use a String like `resource_type[resource_name]' or a Chef::Resource object"
end
end
+ def self.from_hash(o)
+ collection = new
+ rl = o["instance_vars"]["@resources_by_key"]
+ resources = rl.merge(rl) { |k, r| Chef::Resource.from_hash(r) }
+ collection.instance_variable_set(:@resources_by_key, resources)
+ collection
+ end
+
private
def create_key(resource_type, instance_name)
@@ -139,34 +150,50 @@ class Chef
end
def find_resource_by_hash(arg)
- results = Array.new
+ results = []
arg.each do |resource_type, name_list|
- instance_names = name_list.kind_of?(Array) ? name_list : [ name_list ]
+ instance_names = name_list.is_a?(Array) ? name_list : [ name_list ]
instance_names.each do |instance_name|
results << lookup(create_key(resource_type, instance_name))
end
end
- return results
+ results
end
def find_resource_by_string(arg)
- results = Array.new
- case arg
- when MULTIPLE_RESOURCE_MATCH
- resource_type = $1
- arg =~ /^.+\[(.+)\]$/
- resource_list = $1
- resource_list.split(",").each do |instance_name|
- results << lookup(create_key(resource_type, instance_name))
- end
- when SINGLE_RESOURCE_MATCH
+ begin
+ if arg =~ SINGLE_RESOURCE_MATCH
resource_type = $1
name = $2
- results << lookup(create_key(resource_type, name))
+ return [ lookup(create_key(resource_type, name)) ]
+ end
+ rescue Chef::Exceptions::ResourceNotFound => e
+ if arg =~ MULTIPLE_RESOURCE_MATCH
+ begin
+ results = []
+ resource_type = $1
+ arg =~ /^.+\[(.+)\]$/
+ resource_list = $1
+ resource_list.split(",").each do |instance_name|
+ results << lookup(create_key(resource_type, instance_name))
+ end
+ Chef.deprecated(:multiresource_match, "The resource_collection multi-resource syntax is deprecated")
+ return results
+ rescue Chef::Exceptions::ResourceNotFound
+ raise e
+ end
else
- raise ArgumentError, "Bad string format #{arg}, you must have a string like resource_type[name]!"
+ raise e
+ end
+ end
+
+ if arg =~ NAMELESS_RESOURCE_MATCH
+ resource_type = $1
+ name = ""
+ return [ lookup(create_key(resource_type, name)) ]
end
- return results
+
+ raise ArgumentError, "Bad string format #{arg}, you must have a string like resource_type[name]!"
end
end
end
diff --git a/lib/chef/resource_collection/stepable_iterator.rb b/lib/chef/resource_collection/stepable_iterator.rb
index 958ffa28cb..24a8f676d5 100644
--- a/lib/chef/resource_collection/stepable_iterator.rb
+++ b/lib/chef/resource_collection/stepable_iterator.rb
@@ -20,8 +20,7 @@ class Chef
class StepableIterator
def self.for_collection(new_collection)
- instance = new(new_collection)
- instance
+ new(new_collection)
end
attr_accessor :collection
@@ -82,6 +81,7 @@ class Chef
def step
return nil if @position == size
+
call_iterator_block
@position += 1
end
diff --git a/lib/chef/resource_definition.rb b/lib/chef/resource_definition.rb
index aa114af46c..e97ff52b6a 100644
--- a/lib/chef/resource_definition.rb
+++ b/lib/chef/resource_definition.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/from_file"
-require "chef/mixin/params_validate"
+require_relative "mixin/from_file"
+require_relative "mixin/params_validate"
class Chef
class ResourceDefinition
@@ -29,20 +29,22 @@ class Chef
def initialize(node = nil)
@name = nil
- @params = Hash.new
+ @params = {}
@recipe = nil
@node = node
end
def define(resource_name, prototype_params = nil, &block)
- unless resource_name.kind_of?(Symbol)
+ unless resource_name.is_a?(Symbol)
raise ArgumentError, "You must use a symbol when defining a new resource!"
end
+
@name = resource_name
if prototype_params
- unless prototype_params.kind_of?(Hash)
+ unless prototype_params.is_a?(Hash)
raise ArgumentError, "You must pass a hash as the prototype parameters for a definition."
end
+
@params = prototype_params
end
if Kernel.block_given?
@@ -62,7 +64,7 @@ class Chef
end
def to_s
- "#{name}"
+ (name).to_s
end
end
end
diff --git a/lib/chef/resource_definition_list.rb b/lib/chef/resource_definition_list.rb
index 22751249e4..709d543668 100644
--- a/lib/chef/resource_definition_list.rb
+++ b/lib/chef/resource_definition_list.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/from_file"
-require "chef/resource_definition"
+require_relative "mixin/from_file"
+require_relative "resource_definition"
class Chef
class ResourceDefinitionList
@@ -26,7 +26,7 @@ class Chef
attr_accessor :defines
def initialize
- @defines = Hash.new
+ @defines = {}
end
def define(resource_name, prototype_params = nil, &block)
diff --git a/lib/chef/resource_inspector.rb b/lib/chef/resource_inspector.rb
new file mode 100644
index 0000000000..6d320f4202
--- /dev/null
+++ b/lib/chef/resource_inspector.rb
@@ -0,0 +1,118 @@
+# Copyright:: Copyright (c) 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_relative "cookbook_loader"
+require_relative "cookbook/file_vendor"
+require_relative "cookbook/file_system_file_vendor"
+require_relative "resource/lwrp_base"
+require_relative "run_context"
+require_relative "node"
+require_relative "resources"
+require_relative "json_compat"
+
+class Chef
+ module ResourceInspector
+ def self.get_default(default)
+ if default.is_a?(Chef::DelayedEvaluator)
+ # ideally we'd get the block we pass to `lazy`, but the best we can do
+ # is to get the source location, which then results in reparsing the source
+ # code for the resource ourselves and just no
+ "lazy default"
+ else
+ default.is_a?(Symbol) ? default.inspect : default # inspect properly returns symbols
+ end
+ end
+
+ def self.extract_resource(resource, complete = false)
+ data = {}
+ data[:description] = resource.description
+ # data[:deprecated] = resource.deprecated || false
+ data[:default_action] = resource.default_action
+ data[:actions] = resource.allowed_actions
+ data[:examples] = resource.examples
+ data[:introduced] = resource.introduced
+ data[:preview] = resource.preview_resource
+
+ properties = unless complete
+ resource.properties.reject { |_, k| k.options[:declared_in] == Chef::Resource || k.options[:skip_docs] }
+ else
+ resource.properties.reject { |_, k| k.options[:skip_docs] }
+ end
+
+ data[:properties] = properties.each_with_object([]) do |(n, k), acc|
+ opts = k.options
+ acc << { name: n, description: opts[:description],
+ introduced: opts[:introduced], is: opts[:is],
+ deprecated: opts[:deprecated] || false,
+ required: opts[:required] || false,
+ default: opts[:default_description] || get_default(opts[:default]),
+ name_property: opts[:name_property] || false,
+ equal_to: sort_equal_to(opts[:equal_to]) }
+ end
+ data
+ end
+
+ def self.sort_equal_to(equal_to)
+ Array(equal_to).sort.map(&:inspect)
+ rescue ArgumentError
+ Array(equal_to).map(&:inspect)
+ end
+
+ def self.extract_cookbook(path, complete)
+ path = File.expand_path(path)
+ dir, name = File.split(path)
+ Chef::Cookbook::FileVendor.fetch_from_disk(path)
+ loader = Chef::CookbookLoader.new(dir)
+ cookbook = loader.load_cookbook(name)
+ resources = cookbook.files_for(:resources)
+
+ resources.each_with_object({}) do |r, res|
+ pth = r["full_path"]
+ cur = Chef::Resource::LWRPBase.build_from_file(name, pth, Chef::RunContext.new(Chef::Node.new, nil, nil))
+ res[cur.resource_name] = extract_resource(cur, complete)
+ end
+ end
+
+ # If we're given no resources, dump all of Chef's built ins
+ # otherwise, if we have a path then extract all the resources from the cookbook
+ # or else do a list of built in resources
+ #
+ # @param arguments [Array, String] One of more paths to a cookbook or a resource file to inspect
+ # @param complete [TrueClass, FalseClass] Whether to show properties defined in the base Resource class
+ # @return [String] JSON formatting of all resources
+ def self.inspect(arguments = [], complete: false)
+ output = if arguments.empty?
+ ObjectSpace.each_object(Class).select { |k| k < Chef::Resource }.each_with_object({}) { |klass, acc| acc[klass.resource_name] = extract_resource(klass) }
+ else
+ Array(arguments).each_with_object({}) do |arg, acc|
+ if File.directory?(arg)
+ extract_cookbook(arg, complete).each { |k, v| acc[k] = v }
+ else
+ r = Chef::ResourceResolver.resolve(arg.to_sym)
+ acc[r.resource_name] = extract_resource(r, complete)
+ end
+ end
+ end
+
+ Chef::JSONCompat.to_json_pretty(output)
+ end
+
+ def self.start
+ puts inspect(ARGV, complete: true)
+ end
+
+ end
+end
diff --git a/lib/chef/resource_reporter.rb b/lib/chef/resource_reporter.rb
index 8422870e2a..4051ac2f49 100644
--- a/lib/chef/resource_reporter.rb
+++ b/lib/chef/resource_reporter.rb
@@ -1,9 +1,9 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
# Author:: Prajakta Purohit (prajakta@chef.io>)
-# Auther:: Tyler Cloke (<tyler@opscode.com>)
+# Author:: Tyler Cloke (<tyler@opscode.com>)
#
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,95 +19,44 @@
# limitations under the License.
#
-require "uri"
-require "securerandom"
-require "chef/event_dispatch/base"
+require_relative "event_dispatch/base"
class Chef
class ResourceReporter < EventDispatch::Base
+ def for_json(action_record)
+ new_resource = action_record.new_resource
+ current_resource = action_record.current_resource
- ResourceReport = Struct.new(:new_resource,
- :current_resource,
- :action,
- :exception,
- :elapsed_time) do
-
- def self.new_with_current_state(new_resource, action, current_resource)
- report = new
- report.new_resource = new_resource
- report.action = action
- report.current_resource = current_resource
- report
- end
-
- def self.new_for_exception(new_resource, action)
- report = new
- report.new_resource = new_resource
- report.action = action
- report
- end
-
- # Future: Some resources store state information that does not convert nicely
- # to json. We can't call a resource's state method here, since there are conflicts
- # with some LWRPs, so we can't override a resource's state method to return
- # json-friendly state data.
- #
- # The registry key resource returns json-friendly state data through its state
- # attribute, and uses a read-only variable for fetching true state data. If
- # we have conflicts with other resources reporting json incompatible state, we
- # may want to extend the state_attrs API with the ability to rename POST'd
- # attrs.
- def for_json
- as_hash = {}
- 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"] = 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?
-
- # TODO: rename as "action"
- as_hash["result"] = action.to_s
- if success?
- else
- #as_hash["result"] = "failed"
- end
- if new_resource.cookbook_name
- as_hash["cookbook_name"] = new_resource.cookbook_name
- as_hash["cookbook_version"] = new_resource.cookbook_version.version
- end
+ as_hash = {}
+ 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"] = new_resource.state_for_resource_reporter
+ as_hash["before"] = current_resource ? current_resource.state_for_resource_reporter : {}
+ as_hash["duration"] = ( action_record.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?
- as_hash
+ # TODO: rename as "action"
+ as_hash["result"] = action_record.action.to_s
+ if new_resource.cookbook_name
+ as_hash["cookbook_name"] = new_resource.cookbook_name
+ as_hash["cookbook_version"] = new_resource.cookbook_version.version
end
- def finish
- self.elapsed_time = new_resource.elapsed_time
- end
-
- def success?
- !self.exception
- end
- end # End class ResouceReport
+ as_hash
+ end
- attr_reader :updated_resources
attr_reader :status
attr_reader :exception
- attr_reader :run_id
attr_reader :error_descriptions
+ attr_reader :action_collection
+ attr_reader :rest_client
- PROTOCOL_VERSION = "0.1.0"
+ PROTOCOL_VERSION = "0.1.0".freeze
def initialize(rest_client)
- if Chef::Config[:enable_reporting] && !Chef::Config[:why_run]
- @reporting_enabled = true
- else
- @reporting_enabled = false
- end
- @updated_resources = []
- @total_res_count = 0
- @pending_update = nil
+ @pending_update = nil
@status = "success"
@exception = nil
@rest_client = rest_client
@@ -121,8 +70,8 @@ class Chef
if reporting_enabled?
begin
resource_history_url = "reports/nodes/#{node_name}/runs"
- server_response = @rest_client.post(resource_history_url, { :action => :start, :run_id => run_id,
- :start_time => start_time.to_s }, headers)
+ 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)
end
@@ -154,55 +103,17 @@ class Chef
Chef::Log.info(message + reason + reporting_status)
else
reporting_status = "Disabling reporting for run."
- Chef::Log.debug(message + reason + reporting_status)
+ Chef::Log.trace(message + reason + reporting_status)
end
end
- @reporting_enabled = false
+ @runs_endpoint_failed = true
end
def run_id
@run_status.run_id
end
- def resource_current_state_loaded(new_resource, action, current_resource)
- unless nested_resource?(new_resource)
- @pending_update = ResourceReport.new_with_current_state(new_resource, action, current_resource)
- end
- end
-
- def resource_up_to_date(new_resource, action)
- @total_res_count += 1
- @pending_update = nil unless nested_resource?(new_resource)
- end
-
- def resource_skipped(resource, action, conditional)
- @total_res_count += 1
- @pending_update = nil unless nested_resource?(resource)
- end
-
- def resource_updated(new_resource, action)
- @total_res_count += 1
- end
-
- def resource_failed(new_resource, action, exception)
- @total_res_count += 1
- unless nested_resource?(new_resource)
- @pending_update ||= ResourceReport.new_for_exception(new_resource, action)
- @pending_update.exception = exception
- end
- description = Formatters::ErrorMapper.resource_failed(new_resource, action, exception)
- @error_descriptions = description.for_json
- end
-
- def resource_completed(new_resource)
- if @pending_update && !nested_resource?(new_resource)
- @pending_update.finish
- @updated_resources << @pending_update
- @pending_update = nil
- end
- end
-
def run_completed(node)
@status = "success"
post_reporting_data
@@ -222,17 +133,22 @@ class Chef
@expanded_run_list = run_list_expansion
end
+ def action_collection_registration(action_collection)
+ @action_collection = action_collection
+ action_collection.register(self) if reporting_enabled?
+ end
+
def post_reporting_data
if reporting_enabled?
run_data = prepare_run_data
resource_history_url = "reports/nodes/#{node_name}/runs/#{run_id}"
Chef::Log.info("Sending resource update report (run-id: #{run_id})")
- Chef::Log.debug run_data.inspect
+ Chef::Log.trace run_data.inspect
compressed_data = encode_gzip(Chef::JSONCompat.to_json(run_data))
- Chef::Log.debug("Sending compressed run data...")
+ Chef::Log.trace("Sending compressed run data...")
# Since we're posting compressed data we can not directly call post which expects JSON
begin
- @rest_client.raw_request(:POST, resource_history_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)
@@ -242,7 +158,7 @@ class Chef
end
end
else
- Chef::Log.debug("Server doesn't support resource history, skipping resource report.")
+ Chef::Log.trace("Server doesn't support resource history, skipping resource report.")
end
end
@@ -263,15 +179,24 @@ class Chef
@run_status.end_time
end
+ # get only the top level resources and strip out the subcollections
+ def updated_resources
+ @updated_resources ||= action_collection&.filtered_collection(max_nesting: 0, up_to_date: false, skipped: false, unprocessed: false) || {}
+ end
+
+ def total_res_count
+ updated_resources.count
+ end
+
def prepare_run_data
run_data = {}
run_data["action"] = "end"
- run_data["resources"] = updated_resources.map do |resource_record|
- resource_record.for_json
+ run_data["resources"] = updated_resources.map do |action_record|
+ for_json(action_record)
end
run_data["status"] = @status
run_data["run_list"] = Chef::JSONCompat.to_json(@run_status.node.run_list)
- run_data["total_res_count"] = @total_res_count.to_s
+ run_data["total_res_count"] = total_res_count.to_s
run_data["data"] = {}
run_data["start_time"] = start_time.to_s
run_data["end_time"] = end_time.to_s
@@ -303,18 +228,10 @@ class Chef
@error_descriptions = description.for_json
end
- def reporting_enabled?
- @reporting_enabled
- end
-
private
- # If we are getting messages about a resource while we are in the middle of
- # another resource's update, we assume that the nested resource is just the
- # implementation of a provider, and we want to hide it from the reporting
- # output.
- def nested_resource?(new_resource)
- @pending_update && @pending_update.new_resource != new_resource
+ def reporting_enabled?
+ Chef::Config[:enable_reporting] && !Chef::Config[:why_run] && !@runs_endpoint_failed
end
def encode_gzip(data)
diff --git a/lib/chef/resource_resolver.rb b/lib/chef/resource_resolver.rb
index 769272d637..df32b18164 100644
--- a/lib/chef/resource_resolver.rb
+++ b/lib/chef/resource_resolver.rb
@@ -1,6 +1,6 @@
#
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/exceptions"
-require "chef/platform/resource_priority_map"
-require "chef/mixin/convert_to_class_name"
+require_relative "exceptions"
+require_relative "platform/resource_priority_map"
+require_relative "mixin/convert_to_class_name"
class Chef
class ResourceResolver
@@ -29,8 +29,8 @@ class Chef
# @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
+ def self.resolve(resource_name, node: nil)
+ new(node, resource_name).resolve
end
#
@@ -40,11 +40,9 @@ class Chef
# @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
+ def self.list(resource_name, node: nil)
+ new(node, resource_name).list
end
include Chef::Mixin::ConvertToClassName
@@ -54,14 +52,7 @@ class Chef
# @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.
@@ -69,27 +60,24 @@ class Chef
# @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)
+ def initialize(node, resource_name)
@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}"
+ Chef::Log.trace "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}"
+ Chef::Log.trace "Resource for #{resource_name} is #{handler}"
else
- Chef::Log.debug "Dynamic resource resolver FAILED to resolve a resource for #{resource_name}"
+ Chef::Log.trace "Dynamic resource resolver FAILED to resolve a resource for #{resource_name}"
end
handler
@@ -97,7 +85,7 @@ class Chef
# @api private
def list
- Chef::Log.debug "Resources for generic #{resource_name} resource enabled on node include: #{prioritized_handlers}"
+ Chef::Log.trace "Resources for generic #{resource_name} resource enabled on node include: #{prioritized_handlers}"
prioritized_handlers
end
@@ -140,7 +128,7 @@ class Chef
# @api private
def potential_handlers
- handler_map.list(node, resource_name, canonical: canonical).uniq
+ handler_map.list(node, resource_name).uniq
end
def enabled_handlers
@@ -151,7 +139,7 @@ class Chef
@prioritized_handlers ||= begin
enabled_handlers = self.enabled_handlers
- prioritized = priority_map.list(node, resource_name, canonical: canonical).flatten(1)
+ prioritized = priority_map.list(node, resource_name).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
@@ -161,25 +149,5 @@ class Chef
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 2afd47a8f4..843d5610b8 100644
--- a/lib/chef/resources.rb
+++ b/lib/chef/resources.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,82 +16,158 @@
# limitations under the License.
#
-require "chef/resource/apt_package"
-require "chef/resource/apt_repository"
-require "chef/resource/apt_update"
-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/launchd"
-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/systemd_unit"
-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/user/aix_user"
-require "chef/resource/user/dscl_user"
-require "chef/resource/user/linux_user"
-require "chef/resource/user/pw_user"
-require "chef/resource/user/solaris_user"
-require "chef/resource/user/windows_user"
-require "chef/resource/whyrun_safe_ruby_block"
-require "chef/resource/windows_package"
-require "chef/resource/yum_package"
-require "chef/resource/yum_repository"
-require "chef/resource/lwrp_base"
-require "chef/resource/bff_package"
-require "chef/resource/zypper_package"
+require_relative "resource/alternatives"
+require_relative "resource/apt_package"
+require_relative "resource/apt_preference"
+require_relative "resource/apt_repository"
+require_relative "resource/apt_update"
+require_relative "resource/archive_file"
+require_relative "resource/bash"
+require_relative "resource/batch"
+require_relative "resource/breakpoint"
+require_relative "resource/build_essential"
+require_relative "resource/cookbook_file"
+require_relative "resource/chef_client_config"
+require_relative "resource/chef_client_cron"
+require_relative "resource/chef_client_launchd"
+require_relative "resource/chef_client_scheduled_task"
+require_relative "resource/chef_client_systemd_timer"
+require_relative "resource/chef_client_trusted_certificate"
+require_relative "resource/chef_gem"
+require_relative "resource/chef_handler"
+require_relative "resource/chef_sleep"
+require_relative "resource/chef_vault_secret"
+require_relative "resource/chocolatey_config"
+require_relative "resource/chocolatey_feature"
+require_relative "resource/chocolatey_package"
+require_relative "resource/chocolatey_source"
+require_relative "resource/cron/cron"
+require_relative "resource/cron_access"
+require_relative "resource/cron/cron_d"
+require_relative "resource/csh"
+require_relative "resource/directory"
+require_relative "resource/dmg_package"
+require_relative "resource/dpkg_package"
+require_relative "resource/dnf_package"
+require_relative "resource/dsc_script"
+require_relative "resource/dsc_resource"
+require_relative "resource/execute"
+require_relative "resource/file"
+require_relative "resource/freebsd_package"
+require_relative "resource/ips_package"
+require_relative "resource/gem_package"
+require_relative "resource/scm/git"
+require_relative "resource/group"
+require_relative "resource/http_request"
+require_relative "resource/hostname"
+require_relative "resource/homebrew_cask"
+require_relative "resource/homebrew_package"
+require_relative "resource/homebrew_tap"
+require_relative "resource/homebrew_update"
+require_relative "resource/ifconfig"
+require_relative "resource/kernel_module"
+require_relative "resource/ksh"
+require_relative "resource/launchd"
+require_relative "resource/link"
+require_relative "resource/locale"
+require_relative "resource/log"
+require_relative "resource/macports_package"
+require_relative "resource/macos_userdefaults"
+require_relative "resource/mdadm"
+require_relative "resource/mount"
+require_relative "resource/notify_group"
+require_relative "resource/ohai"
+require_relative "resource/ohai_hint"
+require_relative "resource/openbsd_package"
+require_relative "resource/openssl_dhparam"
+require_relative "resource/openssl_ec_private_key"
+require_relative "resource/openssl_ec_public_key"
+require_relative "resource/openssl_rsa_private_key"
+require_relative "resource/openssl_rsa_public_key"
+require_relative "resource/openssl_x509_certificate"
+require_relative "resource/openssl_x509_crl"
+require_relative "resource/openssl_x509_request"
+require_relative "resource/package"
+require_relative "resource/pacman_package"
+require_relative "resource/paludis_package"
+require_relative "resource/perl"
+require_relative "resource/plist"
+require_relative "resource/portage_package"
+require_relative "resource/powershell_package_source"
+require_relative "resource/powershell_script"
+require_relative "resource/osx_profile"
+require_relative "resource/python"
+require_relative "resource/reboot"
+require_relative "resource/registry_key"
+require_relative "resource/remote_directory"
+require_relative "resource/remote_file"
+require_relative "resource/rhsm_errata_level"
+require_relative "resource/rhsm_errata"
+require_relative "resource/rhsm_register"
+require_relative "resource/rhsm_repo"
+require_relative "resource/rhsm_subscription"
+require_relative "resource/rpm_package"
+require_relative "resource/snap_package"
+require_relative "resource/solaris_package"
+require_relative "resource/route"
+require_relative "resource/ruby"
+require_relative "resource/ruby_block"
+require_relative "resource/script"
+require_relative "resource/service"
+require_relative "resource/sudo"
+require_relative "resource/sysctl"
+require_relative "resource/swap_file"
+require_relative "resource/systemd_unit"
+require_relative "resource/ssh_known_hosts_entry"
+require_relative "resource/windows_service"
+require_relative "resource/scm/subversion"
+require_relative "resource/smartos_package"
+require_relative "resource/template"
+require_relative "resource/user"
+require_relative "resource/user/aix_user"
+require_relative "resource/user/dscl_user"
+require_relative "resource/user/linux_user"
+require_relative "resource/user/mac_user"
+require_relative "resource/user/pw_user"
+require_relative "resource/user/solaris_user"
+require_relative "resource/user/windows_user"
+require_relative "resource/user_ulimit"
+require_relative "resource/whyrun_safe_ruby_block"
+require_relative "resource/windows_env"
+require_relative "resource/windows_package"
+require_relative "resource/yum_package"
+require_relative "resource/yum_repository"
+require_relative "resource/lwrp_base"
+require_relative "resource/bff_package"
+require_relative "resource/zypper_package"
+require_relative "resource/zypper_repository"
+require_relative "resource/cab_package"
+require_relative "resource/powershell_package"
+require_relative "resource/msu_package"
+require_relative "resource/windows_ad_join"
+require_relative "resource/windows_audit_policy"
+require_relative "resource/windows_auto_run"
+require_relative "resource/windows_certificate"
+require_relative "resource/windows_dfs_folder"
+require_relative "resource/windows_dfs_namespace"
+require_relative "resource/windows_dfs_server"
+require_relative "resource/windows_dns_record"
+require_relative "resource/windows_dns_zone"
+require_relative "resource/windows_feature"
+require_relative "resource/windows_feature_dism"
+require_relative "resource/windows_feature_powershell"
+require_relative "resource/windows_firewall_profile"
+require_relative "resource/windows_firewall_rule"
+require_relative "resource/windows_font"
+require_relative "resource/windows_pagefile"
+require_relative "resource/windows_path"
+require_relative "resource/windows_printer"
+require_relative "resource/windows_printer_port"
+require_relative "resource/windows_share"
+require_relative "resource/windows_shortcut"
+require_relative "resource/windows_task"
+require_relative "resource/windows_uac"
+require_relative "resource/windows_workgroup"
+require_relative "resource/timezone"
+require_relative "resource/windows_user_privilege"
+require_relative "resource/windows_security_policy" \ No newline at end of file
diff --git a/lib/chef/rest.rb b/lib/chef/rest.rb
deleted file mode 100644
index a3c5c66b8a..0000000000
--- a/lib/chef/rest.rb
+++ /dev/null
@@ -1,209 +0,0 @@
-#--
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Thom May (<thom@clearairturbulence.org>)
-# Author:: Nuo Yan (<nuo@chef.io>)
-# Author:: Christopher Brown (<cb@chef.io>)
-# Author:: Christopher Walters (<cw@chef.io>)
-# Copyright:: Copyright 2009-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 "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"
-
-class Chef
-
- # == Chef::REST
- # Chef's custom REST client with built-in JSON support and RSA signed header
- # authentication.
- class REST < HTTP
-
- # Backwards compatibility for things that use
- # Chef::REST::RESTRequest or its constants
- RESTRequest = HTTP::HTTPRequest
-
- attr_accessor :url, :cookies, :sign_on_redirect, :redirect_limit
-
- attr_reader :authenticator
-
- # Create a REST client object. The supplied +url+ is used as the base for
- # all subsequent requests. For example, when initialized with a base url
- # http://localhost:4000, a call to +get_rest+ with 'nodes' will make an
- # HTTP GET request to http://localhost:4000/nodes
- def initialize(url, client_name = Chef::Config[:node_name], signing_key_filename = Chef::Config[:client_key], options = {})
- Chef.log_deprecation("Chef::REST is deprecated. Please use Chef::ServerAPI, or investigate Ridley or ChefAPI.")
-
- 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)
- @authenticator = Authenticator.new(options)
- @request_id = RemoteRequestID.new(options)
-
- @middlewares << JSONInput.new(options)
- @middlewares << JSONToModelOutput.new(options)
- @middlewares << CookieManager.new(options)
- @middlewares << @decompressor
- @middlewares << @authenticator
- @middlewares << @request_id
-
- # ValidateContentLength should come after Decompressor
- # because the order of middlewares is reversed when handling
- # responses.
- @middlewares << ValidateContentLength.new(options)
- end
-
- def signing_key_filename
- authenticator.signing_key_filename
- end
-
- def auth_credentials
- authenticator.auth_credentials
- end
-
- def client_name
- authenticator.client_name
- end
-
- def signing_key
- authenticator.raw_key
- end
-
- def sign_requests?
- authenticator.sign_requests?
- end
-
- # Send an HTTP GET request to the path
- #
- # Using this method to +fetch+ a file is considered deprecated.
- #
- # === Parameters
- # path:: The path to GET
- # raw:: Whether you want the raw body returned, or JSON inflated. Defaults
- # to JSON inflated.
- def get(path, raw = false, headers = {})
- if raw
- streaming_request(path, headers)
- else
- request(:GET, path, headers)
- end
- end
-
- alias :get_rest :get
-
- alias :delete_rest :delete
-
- alias :post_rest :post
-
- alias :put_rest :put
-
- # Streams a download to a tempfile, then yields the tempfile to a block.
- # After the download, the tempfile will be closed and unlinked.
- # If you rename the tempfile, it will not be deleted.
- # Beware that if the server streams infinite content, this method will
- # stream it until you run out of disk space.
- def fetch(path, headers = {})
- streaming_request(create_url(path), headers) { |tmp_file| yield tmp_file }
- end
-
- alias :api_request :request
-
- # Do a HTTP request where no middleware is loaded (e.g. JSON input/output
- # conversion) but the standard Chef Authentication headers are added to the
- # request.
- def raw_http_request(method, path, headers, data)
- url = create_url(path)
- method, url, headers, data = @authenticator.handle_request(method, url, headers, data)
- method, url, headers, data = @request_id.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
-
- # Deprecated:
- # Responsibilities of this method have been split up. The #http_client is
- # now responsible for making individual requests, while
- # #retrying_http_errors handles error/retry logic.
- def retriable_http_request(method, url, req_body, headers)
- rest_request = Chef::HTTP::HTTPRequest.new(method, url, req_body, headers)
-
- Chef::Log.debug("Sending HTTP request via #{method} to #{url.host}:#{url.port}#{rest_request.path}")
-
- retrying_http_errors(url) do
- yield rest_request
- end
- end
-
- # Customized streaming behavior; sets the accepted content type to "*/*"
- # if not otherwise specified for compatibility purposes
- def streaming_request(url, headers, &block)
- headers["Accept"] ||= "*/*"
- super
- end
-
- alias :retriable_rest_request :retriable_http_request
-
- def follow_redirect
- unless @sign_on_redirect
- @authenticator.sign_request = false
- end
- super
- ensure
- @authenticator.sign_request = true
- end
-
- public :create_url
-
- ############################################################################
- # DEPRECATED
- ############################################################################
-
- def decompress_body(body)
- @decompressor.decompress_body(body)
- end
-
- def authentication_headers(method, url, json_body = nil)
- authenticator.authentication_headers(method, url, json_body)
- end
-
- end
-end
diff --git a/lib/chef/role.rb b/lib/chef/role.rb
index 331fa614f1..d43e331da4 100644
--- a/lib/chef/role.rb
+++ b/lib/chef/role.rb
@@ -2,7 +2,7 @@
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Nuo Yan (<nuo@chef.io>)
# Author:: Christopher Brown (<cb@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/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"
+require_relative "config"
+require_relative "mixin/params_validate"
+require_relative "mixin/from_file"
+require_relative "run_list"
+require_relative "mash"
+require_relative "json_compat"
+require_relative "server_api"
+require_relative "search/query"
class Chef
class Role
@@ -33,8 +33,6 @@ class Chef
include Chef::Mixin::FromFile
include Chef::Mixin::ParamsValidate
- attr_accessor :chef_server_rest
-
# Create a new Chef::Role object.
def initialize(chef_server_rest: nil)
@name = ""
@@ -57,7 +55,7 @@ class Chef
set_or_return(
:name,
arg,
- :regex => /^[\-[:alnum:]_]+$/
+ regex: /^[\-[:alnum:]_]+$/
)
end
@@ -65,7 +63,7 @@ class Chef
set_or_return(
:description,
arg,
- :kind_of => String
+ kind_of: String
)
end
@@ -88,12 +86,12 @@ class Chef
end
def active_run_list_for(environment)
- @env_run_lists.has_key?(environment) ? environment : "_default"
+ @env_run_lists.key?(environment) ? environment : "_default"
end
# Per environment run lists
def env_run_lists(env_run_lists = nil)
- if !env_run_lists.nil?
+ unless env_run_lists.nil?
unless env_run_lists.key?("_default")
msg = "_default key is required in env_run_lists.\n"
msg << "(env_run_lists: #{env_run_lists.inspect})"
@@ -108,7 +106,7 @@ class Chef
alias :env_run_list :env_run_lists
def env_run_lists_add(env_run_lists = nil)
- if !env_run_lists.nil?
+ unless env_run_lists.nil?
env_run_lists.each { |k, v| @env_run_lists[k] = Chef::RunList.new(*Array(v)) }
end
@env_run_lists
@@ -120,7 +118,7 @@ class Chef
set_or_return(
:default_attributes,
arg,
- :kind_of => Hash
+ kind_of: Hash
)
end
@@ -128,14 +126,14 @@ class Chef
set_or_return(
:override_attributes,
arg,
- :kind_of => Hash
+ kind_of: Hash
)
end
- def to_hash
+ def to_h
env_run_lists_without_default = @env_run_lists.dup
env_run_lists_without_default.delete("_default")
- result = {
+ {
"name" => @name,
"description" => @description,
"json_class" => self.class.name,
@@ -143,20 +141,21 @@ class Chef
"override_attributes" => @override_attributes,
"chef_type" => "role",
- #Render to_json correctly for run_list items (both run_list and evn_run_lists)
- #so malformed json does not result
- "run_list" => run_list.run_list.map { |item| item.to_s },
+ # Render to_json correctly for run_list items (both run_list and evn_run_lists)
+ # so malformed json does not result
+ "run_list" => run_list.run_list.map(&:to_s),
"env_run_lists" => env_run_lists_without_default.inject({}) do |accumulator, (k, v)|
- accumulator[k] = v.map { |x| x.to_s }
+ accumulator[k] = v.map(&:to_s)
accumulator
end,
}
- result
end
+ alias_method :to_hash, :to_h
+
# Serialize this object as a hash
def to_json(*a)
- Chef::JSONCompat.to_json(to_hash, *a)
+ Chef::JSONCompat.to_json(to_h, *a)
end
def update_from!(o)
@@ -168,12 +167,6 @@ class Chef
self
end
- # Create a Chef::Role from JSON
- def self.json_create(o)
- Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::Role#from_hash")
- from_hash(o)
- end
-
def self.from_hash(o)
role = new
role.name(o["name"])
@@ -183,7 +176,7 @@ class Chef
# _default run_list is in 'run_list' for newer clients, and
# 'recipes' for older clients.
- env_run_list_hash = { "_default" => (o.has_key?("run_list") ? o["run_list"] : o["recipes"]) }
+ env_run_list_hash = { "_default" => (o.key?("run_list") ? o["run_list"] : o["recipes"]) }
# Clients before 0.10 do not include env_run_lists, so only
# merge if it's there.
@@ -198,7 +191,7 @@ class Chef
# Get the list of all roles from the API.
def self.list(inflate = false)
if inflate
- response = Hash.new
+ response = {}
Chef::Search::Query.new.search(:role) do |n|
response[n.name] = n unless n.nil?
end
@@ -230,8 +223,9 @@ class Chef
def save
begin
chef_server_rest.put("roles/#{@name}", self)
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => e
raise e unless e.response.code == "404"
+
chef_server_rest.post("roles", self)
end
self
@@ -254,18 +248,19 @@ class Chef
paths = Array(Chef::Config[:role_path])
paths.each do |path|
roles_files = Dir.glob(File.join(Chef::Util::PathHelper.escape_glob_dir(path), "**", "**"))
- js_files = roles_files.select { |file| file.match(/\/#{name}\.json$/) }
- rb_files = roles_files.select { |file| file.match(/\/#{name}\.rb$/) }
+ js_files = roles_files.select { |file| file.match(%r{/#{name}\.json$}) }
+ rb_files = roles_files.select { |file| file.match(%r{/#{name}\.rb$}) }
if js_files.count > 1 || rb_files.count > 1
raise Chef::Exceptions::DuplicateRole, "Multiple roles of same type found named #{name}"
end
+
js_path, rb_path = js_files.first, rb_files.first
- if js_path && File.exists?(js_path)
+ if js_path && File.exist?(js_path)
# from_json returns object.class => json_class in the JSON.
hsh = Chef::JSONCompat.parse(IO.read(js_path))
return from_hash(hsh)
- elsif rb_path && File.exists?(rb_path)
+ elsif rb_path && File.exist?(rb_path)
role = Chef::Role.new
role.name(name)
role.from_file(rb_path)
diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb
index 5d29f766c9..75c18f2fcf 100644
--- a/lib/chef/run_context.rb
+++ b/lib/chef/run_context.rb
@@ -2,7 +2,7 @@
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
# Author:: Tim Hinderliter (<tim@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,25 +17,43 @@
# 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 "forwardable"
+require_relative "resource_collection"
+require_relative "cookbook_version"
+require_relative "node"
+require_relative "role"
+require_relative "log"
+require_relative "recipe"
+require_relative "run_context/cookbook_compiler"
+require_relative "event_dispatch/events_output_stream"
+require_relative "train_transport"
+require_relative "exceptions"
+require "forwardable" unless defined?(Forwardable)
+autoload :Set, "set"
class Chef
- # == Chef::RunContext
# Value object that loads and tracks the context of a Chef run
class RunContext
+ extend Forwardable
+
#
# Global state
#
+ # Common rest object for using to talk to the Chef Server, this strictly 'validates' utf8
+ # and will throw. (will be nil on solo-legacy runs)
+ #
+ # @return [Chef::ServerAPI]
+ #
+ attr_accessor :rest
+
+ # Common rest object for using to talk to the Chef Server, this has utf8 sanitization turned
+ # on and will replace invalid utf8 with valid characters. (will be nil on solo-legacy runs)
+ #
+ # @return [Chef::ServerAPI]
+ #
+ attr_accessor :rest_clean
+
#
# The node for this run
#
@@ -58,14 +76,12 @@ class Chef
#
attr_reader :definitions
- #
# Event dispatcher for this run.
#
# @return [Chef::EventDispatch::Dispatcher]
#
- attr_reader :events
+ attr_accessor :events
- #
# Hash of factoids for a reboot request.
#
# @return [Hash]
@@ -76,7 +92,6 @@ class Chef
# Scoped state
#
- #
# The parent run context.
#
# @return [Chef::RunContext] The parent run context, or `nil` if this is the
@@ -84,7 +99,16 @@ class Chef
#
attr_reader :parent_run_context
+ # The root run context.
#
+ # @return [Chef::RunContext] The root run context.
+ #
+ def root_run_context
+ rc = self
+ rc = rc.parent_run_context until rc.parent_run_context.nil?
+ rc
+ end
+
# The collection of resources intended to be converged (and able to be
# notified).
#
@@ -94,16 +118,20 @@ class Chef
#
attr_reader :resource_collection
+ # Handle to the global action_collection of executed actions for reporting / data_collector /etc
#
- # The list of control groups to execute during the audit phase
+ # @return [Chef::ActionCollection
+ #
+ attr_accessor :action_collection
+
+ # Pointer back to the Chef::Runner that created this
#
- attr_reader :audits
+ attr_accessor :runner
#
# Notification handling
#
- #
# A Hash containing the before notifications triggered by resources
# during the converge phase of the chef run.
#
@@ -112,7 +140,6 @@ class Chef
#
attr_reader :before_notification_collection
- #
# A Hash containing the immediate notifications triggered by resources
# during the converge phase of the chef run.
#
@@ -121,7 +148,6 @@ class Chef
#
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.
#
@@ -130,7 +156,6 @@ class Chef
#
attr_reader :delayed_notification_collection
- #
# An Array containing the delayed (end of run) notifications triggered by
# resources during the converge phase of the chef run.
#
@@ -138,6 +163,22 @@ class Chef
#
attr_reader :delayed_actions
+ # A Set keyed by the string name, of all the resources that are updated. We do not
+ # track actions or individual resource objects, since this matches the behavior of
+ # the notification collections which are keyed by Strings.
+ #
+ attr_reader :updated_resources
+
+ # @return [Boolean] If the resource_collection is in unified_mode (no separate converge phase)
+ #
+ def_delegator :resource_collection, :unified_mode
+
+ # A child of the root Chef::Log logging object.
+ #
+ # @return Mixlib::Log::Child A child logger
+ #
+ attr_reader :logger
+
# 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.
#
@@ -147,24 +188,30 @@ class Chef
# @param events [EventDispatch::Dispatcher] The event dispatcher for this
# run.
#
- def initialize(node, cookbook_collection, events)
- @node = node
- @cookbook_collection = cookbook_collection
+ def initialize(node = nil, cookbook_collection = nil, events = nil, logger = nil)
@events = events
-
- node.run_context = self
- node.set_cookbook_attribute
-
- @definitions = Hash.new
+ @logger = logger || Chef::Log.with_child
+ self.node = node if node
+ self.cookbook_collection = cookbook_collection if cookbook_collection
+ @definitions = {}
@loaded_recipes_hash = {}
@loaded_attributes_hash = {}
@reboot_info = {}
@cookbook_compiler = nil
- @delayed_actions = []
initialize_child_state
end
+ def node=(node)
+ @node = node
+ node.run_context = self
+ end
+
+ def cookbook_collection=(cookbook_collection)
+ @cookbook_collection = cookbook_collection
+ node.set_cookbook_attribute
+ end
+
#
# Triggers the compile phase of the chef run.
#
@@ -180,30 +227,34 @@ class Chef
# Initialize state that applies to both Chef::RunContext and Chef::ChildRunContext
#
def initialize_child_state
- @audits = {}
@resource_collection = Chef::ResourceCollection.new(self)
@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] = [] }
@delayed_actions = []
+ @updated_resources = Set.new
end
#
# Adds an before notification to the +before_notification_collection+.
#
- # @param [Chef::Resource::Notification] The notification to add.
+ # @param [Chef::Resource::Notification] notification The notification to add.
#
def notifies_before(notification)
# Note for the future, notification.notifying_resource may be an instance
# of Chef::Resource::UnresolvedSubscribes when calling {Resource#subscribes}
# with a string value.
+ if unified_mode && updated_resources.include?(notification.notifying_resource.declared_key)
+ raise Chef::Exceptions::UnifiedModeBeforeSubscriptionEarlierResource.new(notification)
+ end
+
before_notification_collection[notification.notifying_resource.declared_key] << notification
end
#
# Adds an immediate notification to the +immediate_notification_collection+.
#
- # @param [Chef::Resource::Notification] The notification to add.
+ # @param [Chef::Resource::Notification] notification The notification to add.
#
def notifies_immediately(notification)
# Note for the future, notification.notifying_resource may be an instance
@@ -215,53 +266,68 @@ class Chef
#
# Adds a delayed notification to the +delayed_notification_collection+.
#
- # @param [Chef::Resource::Notification] The notification to add.
+ # @param [Chef::Resource::Notification] notification The notification to add.
#
def notifies_delayed(notification)
# Note for the future, notification.notifying_resource may be an instance
# of Chef::Resource::UnresolvedSubscribes when calling {Resource#subscribes}
# with a string value.
+ if unified_mode && updated_resources.include?(notification.notifying_resource.declared_key)
+ add_delayed_action(notification)
+ end
delayed_notification_collection[notification.notifying_resource.declared_key] << notification
end
- #
- # Adds a delayed action to the +delayed_actions+.
+ # Adds a delayed action to the delayed_actions collection
#
def add_delayed_action(notification)
if delayed_actions.any? { |existing_notification| existing_notification.duplicates?(notification) }
- Chef::Log.info( "#{notification.notifying_resource} not queuing delayed action #{notification.action} on #{notification.resource}"\
+ logger.info( "#{notification.notifying_resource} not queuing delayed action #{notification.action} on #{notification.resource}"\
" (delayed), as it's already been queued")
else
delayed_actions << notification
end
end
- #
# Get the list of before notifications sent by the given resource.
#
# @return [Array[Notification]]
#
def before_notifications(resource)
- return before_notification_collection[resource.declared_key]
+ key = resource.is_a?(String) ? resource : resource.declared_key
+ before_notification_collection[key]
end
- #
# Get the list of immediate notifications sent by the given resource.
#
# @return [Array[Notification]]
#
def immediate_notifications(resource)
- return immediate_notification_collection[resource.declared_key]
+ key = resource.is_a?(String) ? resource : resource.declared_key
+ immediate_notification_collection[key]
end
+ # Get the list of immediate notifications pending to the given resource
#
+ # @return [Array[Notification]]
+ #
+ def reverse_immediate_notifications(resource)
+ immediate_notification_collection.map do |k, v|
+ v.select do |n|
+ (n.resource.is_a?(String) && n.resource == resource.declared_key) ||
+ n.resource == resource
+ end
+ end.flatten
+ end
+
# Get the list of delayed (end of run) notifications sent by the given
# resource.
#
# @return [Array[Notification]]
#
def delayed_notifications(resource)
- return delayed_notification_collection[resource.declared_key]
+ key = resource.is_a?(String) ? resource : resource.declared_key
+ delayed_notification_collection[key]
end
#
@@ -278,7 +344,7 @@ class Chef
# @see DSL::IncludeRecipe#include_recipe
#
def include_recipe(*recipe_names, current_cookbook: nil)
- result_recipes = Array.new
+ result_recipes = []
recipe_names.flatten.each do |recipe_name|
if result = load_recipe(recipe_name, current_cookbook: current_cookbook)
result_recipes << result
@@ -294,31 +360,31 @@ class Chef
# 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
+ # @param recipe_name [Array[String]] The recipe name (e.g 'my_cookbook' or
# 'my_cookbook::my_resource').
- # @param current_cookbook The cookbook we are currently running in.
+ # @param current_cookbook [String] 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")
+ logger.trace("Loading recipe #{recipe_name} via include_recipe")
cookbook_name, recipe_short_name = Chef::Recipe.parse_recipe_name(recipe_name, current_cookbook: current_cookbook)
if unreachable_cookbook?(cookbook_name) # CHEF-4367
- Chef::Log.warn(<<-ERROR_MESSAGE)
-MissingCookbookDependency:
-Recipe `#{recipe_name}` is not in the run_list, and cookbook '#{cookbook_name}'
-is not a dependency of any cookbook in the run_list. To load this recipe,
-first add a dependency on cookbook '#{cookbook_name}' in the cookbook you're
-including it from in that cookbook's metadata.
-ERROR_MESSAGE
+ logger.warn(<<~ERROR_MESSAGE)
+ MissingCookbookDependency:
+ Recipe `#{recipe_name}` is not in the run_list, and cookbook '#{cookbook_name}'
+ is not a dependency of any cookbook in the run_list. To load this recipe,
+ first add a dependency on cookbook '#{cookbook_name}' in the cookbook you're
+ including it from in that cookbook's metadata.
+ ERROR_MESSAGE
end
if loaded_fully_qualified_recipe?(cookbook_name, recipe_short_name)
- Chef::Log.debug("I am not loading #{recipe_name}, because I have already seen it.")
+ logger.trace("I am not loading #{recipe_name}, because I have already seen it.")
false
else
loaded_recipe(cookbook_name, recipe_short_name)
@@ -338,11 +404,11 @@ ERROR_MESSAGE
# @raise [Chef::Exceptions::RecipeNotFound] If the file does not exist.
#
def load_recipe_file(recipe_file)
- if !File.exist?(recipe_file)
+ unless File.exist?(recipe_file)
raise Chef::Exceptions::RecipeNotFound, "could not find recipe file #{recipe_file}"
end
- Chef::Log.debug("Loading recipe file #{recipe_file}")
+ logger.trace("Loading recipe file #{recipe_file}")
recipe = Chef::Recipe.new("@recipe_files", recipe_file, self)
recipe.from_file(recipe_file)
recipe
@@ -410,7 +476,7 @@ ERROR_MESSAGE
# @return [Boolean] `true` if the recipe has been loaded, `false` otherwise.
#
def loaded_fully_qualified_recipe?(cookbook, recipe)
- loaded_recipes_hash.has_key?("#{cookbook}::#{recipe}")
+ loaded_recipes_hash.key?("#{cookbook}::#{recipe}")
end
#
@@ -445,7 +511,7 @@ ERROR_MESSAGE
# @return [Boolean] `true` if the recipe has been loaded, `false` otherwise.
#
def loaded_fully_qualified_attribute?(cookbook, attribute_file)
- loaded_attributes_hash.has_key?("#{cookbook}::#{attribute_file}")
+ loaded_attributes_hash.key?("#{cookbook}::#{attribute_file}")
end
#
@@ -536,19 +602,42 @@ ERROR_MESSAGE
# 5. raise an exception on any second call.
# 6. ?
def request_reboot(reboot_info)
- Chef::Log.info "Changing reboot status from #{self.reboot_info.inspect} to #{reboot_info.inspect}"
+ logger.info "Changing reboot status from #{self.reboot_info.inspect} to #{reboot_info.inspect}"
@reboot_info = reboot_info
end
+ #
+ # Cancels a pending reboot
+ #
def cancel_reboot
- Chef::Log.info "Changing reboot status from #{reboot_info.inspect} to {}"
+ logger.info "Changing reboot status from #{reboot_info.inspect} to {}"
@reboot_info = {}
end
+ #
+ # Checks to see if a reboot has been requested
+ # @return [Boolean]
+ #
def reboot_requested?
reboot_info.size > 0
end
+ # Remote transport from Train
+ #
+ # @return [Train::Plugins::Transport] The child class for our train transport.
+ #
+ def transport
+ @transport ||= Chef::TrainTransport.new(logger).build_transport
+ end
+
+ # Remote connection object from Train
+ #
+ # @return [Train::Plugins::Transport::BaseConnection]
+ #
+ def transport_connection
+ @transport_connection ||= transport&.connection
+ end
+
#
# Create a child RunContext.
#
@@ -565,27 +654,6 @@ ERROR_MESSAGE
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.
#
@@ -594,12 +662,16 @@ ERROR_MESSAGE
class ChildRunContext < RunContext
extend Forwardable
def_delegators :parent_run_context, *%w{
+ action_collection
+ action_collection=
cancel_reboot
config
cookbook_collection
+ cookbook_collection=
cookbook_compiler
definitions
events
+ events=
has_cookbook_file_in_cookbook?
has_template_in_cookbook?
load
@@ -612,13 +684,21 @@ ERROR_MESSAGE
loaded_recipe?
loaded_recipes
loaded_recipes_hash
+ logger
node
+ node=
open_stream
reboot_info
reboot_info=
reboot_requested?
request_reboot
resolve_attribute
+ rest
+ rest=
+ rest_clean
+ rest_clean=
+ transport
+ transport_connection
unreachable_cookbook?
}
@@ -632,10 +712,10 @@ ERROR_MESSAGE
end
CHILD_STATE = %w{
- audits
- audits=
- create_child
add_delayed_action
+ before_notification_collection
+ before_notifications
+ create_child
delayed_actions
delayed_notification_collection
delayed_notification_collection=
@@ -643,24 +723,28 @@ ERROR_MESSAGE
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
+ notifies_immediately
parent_run_context
resource_collection
resource_collection=
- }.map { |x| x.to_sym }
+ reverse_immediate_notifications
+ root_run_context
+ runner
+ runner=
+ unified_mode
+ updated_resources
+ }.map(&:to_sym)
# Verify that we didn't miss any methods
unless @__skip_method_checking # hook specifically for compat_resource
missing_methods = superclass.instance_methods(false) - instance_methods(false) - CHILD_STATE
- if !missing_methods.empty?
+ unless 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
diff --git a/lib/chef/run_context/cookbook_compiler.rb b/lib/chef/run_context/cookbook_compiler.rb
index b2a8d236a3..27461fea9a 100644
--- a/lib/chef/run_context/cookbook_compiler.rb
+++ b/lib/chef/run_context/cookbook_compiler.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -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"
+autoload :Set, "set"
+require_relative "../log"
+require_relative "../recipe"
+require_relative "../resource/lwrp_base"
+require_relative "../provider/lwrp_base"
+require_relative "../resource_definition_list"
class Chef
class RunContext
@@ -31,12 +31,14 @@ class Chef
class CookbookCompiler
attr_reader :events
attr_reader :run_list_expansion
+ attr_reader :logger
def initialize(run_context, run_list_expansion, events)
@run_context = run_context
@events = events
@run_list_expansion = run_list_expansion
@cookbook_order = nil
+ @logger = run_context.logger.with_child(subsystem: "cookbook_compiler")
end
# Chef::Node object for the current run.
@@ -57,6 +59,7 @@ class Chef
# Run the compile phase of the chef run. Loads files in the following order:
# * Libraries
+ # * Ohai
# * Attributes
# * LWRPs
# * Resource Definitions
@@ -69,6 +72,7 @@ class Chef
# #cookbook_order for more information.
def compile
compile_libraries
+ compile_ohai_plugins
compile_attributes
compile_lwrps
compile_resource_definitions
@@ -87,7 +91,7 @@ class Chef
cookbook = Chef::Recipe.parse_recipe_name(recipe).first
add_cookbook_with_deps(ordered_cookbooks, seen_cookbooks, cookbook)
end
- Chef::Log.debug("Cookbooks to compile: #{ordered_cookbooks.inspect}")
+ logger.debug("Cookbooks to compile: #{ordered_cookbooks.inspect}")
ordered_cookbooks
end
end
@@ -96,16 +100,45 @@ class Chef
def compile_libraries
@events.library_load_start(count_files_by_segment(:libraries))
cookbook_order.each do |cookbook|
- load_libraries_from_cookbook(cookbook)
+ eager_load_libraries = cookbook_collection[cookbook].metadata.eager_load_libraries
+ if eager_load_libraries == true # actually true, not truthy
+ load_libraries_from_cookbook(cookbook)
+ else
+ $LOAD_PATH.unshift File.expand_path("libraries", cookbook_collection[cookbook].root_dir)
+ if eager_load_libraries # we have a String or Array<String> and not false
+ load_libraries_from_cookbook(cookbook, eager_load_libraries)
+ end
+ end
end
@events.library_load_complete
end
+ # Loads Ohai Plugins from cookbooks, and ensure any old ones are
+ # properly cleaned out
+ def compile_ohai_plugins
+ ohai_plugin_count = count_files_by_segment(:ohai)
+ @events.ohai_plugin_load_start(ohai_plugin_count)
+ FileUtils.rm_rf(Chef::Config[:ohai_segment_plugin_path])
+
+ cookbook_order.each do |cookbook|
+ load_ohai_plugins_from_cookbook(cookbook)
+ end
+
+ # Doing a full ohai system check is costly, so only do so if we've loaded additional plugins
+ if ohai_plugin_count > 0
+ # FIXME(log): figure out what the ohai logger looks like here
+ ohai = Ohai::System.new.run_additional_plugins(Chef::Config[:ohai_segment_plugin_path])
+ node.consume_ohai_data(ohai)
+ end
+
+ @events.ohai_plugin_load_complete
+ end
+
# Loads attributes files from cookbooks. Attributes files are loaded
# according to #cookbook_order; within a cookbook, +default.rb+ is loaded
# first, then the remaining attributes files in lexical sort order.
def compile_attributes
- @events.attribute_load_start(count_files_by_segment(:attributes))
+ @events.attribute_load_start(count_files_by_segment(:attributes, "attributes.rb"))
cookbook_order.each do |cookbook|
load_attributes_from_cookbook(cookbook)
end
@@ -136,17 +169,17 @@ class Chef
def compile_recipes
@events.recipe_load_start(run_list_expansion.recipes.size)
run_list_expansion.recipes.each do |recipe|
- begin
- path = resolve_recipe(recipe)
- @run_context.load_recipe(recipe)
- @events.recipe_file_loaded(path, recipe)
- rescue Chef::Exceptions::RecipeNotFound => e
- @events.recipe_not_found(e)
- raise
- rescue Exception => e
- @events.recipe_file_load_failed(path, e, recipe)
- raise
- end
+
+ path = resolve_recipe(recipe)
+ @run_context.load_recipe(recipe)
+ @events.recipe_file_loaded(path, recipe)
+ rescue Chef::Exceptions::RecipeNotFound => e
+ @events.recipe_not_found(e)
+ raise
+ rescue Exception => e
+ @events.recipe_file_load_failed(path, e, recipe)
+ raise
+
end
@events.recipe_load_complete
end
@@ -166,18 +199,29 @@ class Chef
def load_attributes_from_cookbook(cookbook_name)
list_of_attr_files = files_in_cookbook_by_segment(cookbook_name, :attributes).dup
- if default_file = list_of_attr_files.find { |path| File.basename(path) == "default.rb" }
+ root_alias = cookbook_collection[cookbook_name].files_for(:root_files).find { |record| record[:name] == "root_files/attributes.rb" }
+ default_file = list_of_attr_files.find { |path| File.basename(path) == "default.rb" }
+ if root_alias
+ if default_file
+ logger.error("Cookbook #{cookbook_name} contains both attributes.rb and and attributes/default.rb, ignoring attributes/default.rb")
+ list_of_attr_files.delete(default_file)
+ end
+ # The actual root_alias path decoding is handled in CookbookVersion#attribute_filenames_by_short_filename
+ load_attribute_file(cookbook_name.to_s, "default")
+ elsif default_file
list_of_attr_files.delete(default_file)
load_attribute_file(cookbook_name.to_s, default_file)
end
list_of_attr_files.each do |filename|
+ next unless File.extname(filename) == ".rb"
+
load_attribute_file(cookbook_name.to_s, filename)
end
end
def load_attribute_file(cookbook_name, filename)
- Chef::Log.debug("Node #{node.name} loading cookbook #{cookbook_name}'s attribute file #{filename}")
+ logger.trace("Node #{node.name} loading cookbook #{cookbook_name}'s attribute file #{filename}")
attr_file_basename = ::File.basename(filename, ".rb")
node.include_attribute("#{cookbook_name}::#{attr_file_basename}")
rescue Exception => e
@@ -185,31 +229,36 @@ class Chef
raise
end
- def load_libraries_from_cookbook(cookbook_name)
- files_in_cookbook_by_segment(cookbook_name, :libraries).each do |filename|
- next unless File.extname(filename) == ".rb"
- begin
- Chef::Log.debug("Loading cookbook #{cookbook_name}'s library file: #{filename}")
- Kernel.load(filename)
- @events.library_file_loaded(filename)
- rescue Exception => e
- @events.library_file_load_failed(filename, e)
- raise
- end
+ def load_libraries_from_cookbook(cookbook_name, globs = "**/*.rb")
+ each_file_in_cookbook_by_segment(cookbook_name, :libraries, globs) do |filename|
+
+ logger.trace("Loading cookbook #{cookbook_name}'s library file: #{filename}")
+ Kernel.require(filename)
+ @events.library_file_loaded(filename)
+ rescue Exception => e
+ @events.library_file_load_failed(filename, e)
+ raise
+
end
end
def load_lwrps_from_cookbook(cookbook_name)
files_in_cookbook_by_segment(cookbook_name, :providers).each do |filename|
+ next unless File.extname(filename) == ".rb"
+ next if File.basename(filename).match?(/^_/)
+
load_lwrp_provider(cookbook_name, filename)
end
files_in_cookbook_by_segment(cookbook_name, :resources).each do |filename|
+ next unless File.extname(filename) == ".rb"
+ next if File.basename(filename).match?(/^_/)
+
load_lwrp_resource(cookbook_name, filename)
end
end
def load_lwrp_provider(cookbook_name, filename)
- Chef::Log.debug("Loading cookbook #{cookbook_name}'s providers from #{filename}")
+ logger.trace("Loading cookbook #{cookbook_name}'s providers from #{filename}")
Chef::Provider::LWRPBase.build_from_file(cookbook_name, filename, self)
@events.lwrp_file_loaded(filename)
rescue Exception => e
@@ -218,7 +267,7 @@ class Chef
end
def load_lwrp_resource(cookbook_name, filename)
- Chef::Log.debug("Loading cookbook #{cookbook_name}'s resources from #{filename}")
+ logger.trace("Loading cookbook #{cookbook_name}'s resources from #{filename}")
Chef::Resource::LWRPBase.build_from_file(cookbook_name, filename, self)
@events.lwrp_file_loaded(filename)
rescue Exception => e
@@ -226,14 +275,29 @@ class Chef
raise
end
+ def load_ohai_plugins_from_cookbook(cookbook_name)
+ target = Chef::Config[:ohai_segment_plugin_path]
+ files_in_cookbook_by_segment(cookbook_name, :ohai).each do |filename|
+ next unless File.extname(filename) == ".rb"
+
+ logger.trace "Loading Ohai plugin: #{filename} from #{cookbook_name}"
+ target_name = File.join(target, cookbook_name.to_s, File.basename(filename))
+
+ FileUtils.mkdir_p(File.dirname(target_name))
+ FileUtils.cp(filename, target_name)
+ end
+ end
+
def load_resource_definitions_from_cookbook(cookbook_name)
files_in_cookbook_by_segment(cookbook_name, :definitions).each do |filename|
+ next unless File.extname(filename) == ".rb"
+
begin
- Chef::Log.debug("Loading cookbook #{cookbook_name}'s definitions from #{filename}")
+ logger.trace("Loading cookbook #{cookbook_name}'s definitions from #{filename}")
resourcelist = Chef::ResourceDefinitionList.new
resourcelist.from_file(filename)
definitions.merge!(resourcelist.defines) do |key, oldval, newval|
- Chef::Log.info("Overriding duplicate definition #{key}, new definition found in #{filename}")
+ logger.info("Overriding duplicate definition #{key}, new definition found in #{filename}")
newval
end
@events.definition_file_loaded(filename)
@@ -259,23 +323,39 @@ class Chef
ordered_cookbooks << cookbook
end
- def count_files_by_segment(segment)
+ def count_files_by_segment(segment, root_alias = nil)
cookbook_collection.inject(0) do |count, cookbook_by_name|
- count + cookbook_by_name[1].segment_filenames(segment).size
+ count + cookbook_by_name[1].segment_filenames(segment).size + (root_alias ? cookbook_by_name[1].files_for(:root_files).count { |record| record[:name] == root_alias } : 0)
end
end
# Lists the local paths to files in +cookbook+ of type +segment+
# (attribute, recipe, etc.), sorted lexically.
def files_in_cookbook_by_segment(cookbook, segment)
- cookbook_collection[cookbook].segment_filenames(segment).sort
+ cookbook_collection[cookbook].files_for(segment).map { |record| record[:full_path] }.sort
+ end
+
+ # Iterates through all files in given cookbook segment, yielding the full path to the file
+ # if it matches one of the given globs. Returns matching files in lexical sort order. Supports
+ # extended globbing. The segment should not be included in the glob.
+ #
+ def each_file_in_cookbook_by_segment(cookbook, segment, globs)
+ cookbook_collection[cookbook].files_for(segment).sort_by { |record| record[:path] }.each do |record|
+ Array(globs).each do |glob|
+ target = record[:path].delete_prefix("#{segment}/")
+ if File.fnmatch(glob, target, File::FNM_PATHNAME | File::FNM_EXTGLOB | File::FNM_DOTMATCH)
+ yield record[:full_path]
+ break
+ end
+ end
+ end
end
# Yields the name, as a symbol, of each cookbook depended on by
# +cookbook_name+ in lexical sort order.
def each_cookbook_dep(cookbook_name, &block)
cookbook = cookbook_collection[cookbook_name]
- cookbook.metadata.dependencies.keys.sort.map { |x| x.to_sym }.each(&block)
+ cookbook.metadata.dependencies.keys.sort.map(&:to_sym).each(&block)
end
# Given a +recipe_name+, finds the file associated with the recipe.
diff --git a/lib/chef/run_list.rb b/lib/chef/run_list.rb
index 3ac5fab07b..2779dd69aa 100644
--- a/lib/chef/run_list.rb
+++ b/lib/chef/run_list.rb
@@ -4,7 +4,7 @@
# Author:: Tim Hinderliter (<tim@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -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_relative "run_list/run_list_item"
+require_relative "run_list/run_list_expansion"
+require_relative "run_list/versioned_recipe_list"
+require_relative "mixin/params_validate"
class Chef
class RunList
@@ -70,10 +70,11 @@ class Chef
alias :add :<<
def ==(other)
- if other.kind_of?(Chef::RunList)
+ if other.is_a?(Chef::RunList)
other.run_list_items == @run_list_items
else
return false unless other.respond_to?(:size) && (other.size == @run_list_items.size)
+
other_run_list_items = other.dup
other_run_list_items.map! { |item| coerce_to_run_list_item(item) }
@@ -86,7 +87,7 @@ class Chef
end
def for_json
- to_a.map { |item| item.to_s }
+ to_a.map(&:to_s)
end
def to_json(*a)
@@ -122,7 +123,7 @@ class Chef
def reset!(*args)
@run_list_items.clear
args.flatten.each do |item|
- if item.kind_of?(Chef::RunList)
+ if item.is_a?(Chef::RunList)
item.each { |r| self << r }
else
self << item
@@ -152,7 +153,7 @@ class Chef
end
def coerce_to_run_list_item(item)
- item.kind_of?(RunListItem) ? item : parse_entry(item)
+ item.is_a?(RunListItem) ? item : parse_entry(item)
end
def expansion_for_data_source(environment, data_source, opts = {})
diff --git a/lib/chef/run_list/run_list_expansion.rb b/lib/chef/run_list/run_list_expansion.rb
index b895b53523..413d0a3db8 100644
--- a/lib/chef/run_list/run_list_expansion.rb
+++ b/lib/chef/run_list/run_list_expansion.rb
@@ -1,7 +1,7 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
# Author:: Tim Hinderliter (<tim@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,13 +16,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require "chef/mash"
+require_relative "../mash"
-require "chef/mixin/deep_merge"
+require_relative "../mixin/deep_merge"
-require "chef/role"
-require "chef/server_api"
-require "chef/json_compat"
+require_relative "../role"
+require_relative "../server_api"
+require_relative "../json_compat"
class Chef
class RunList
@@ -64,7 +64,7 @@ class Chef
def initialize(environment, run_list_items, source = nil)
@environment = environment
- @missing_roles_with_including_role = Array.new
+ @missing_roles_with_including_role = []
@run_list_items = run_list_items.dup
@source = source
@@ -102,6 +102,7 @@ class Chef
# nil if the role does not exist
def inflate_role(role_name, included_by)
return false if applied_role?(role_name) # Prevent infinite loops
+
applied_role(role_name)
fetch_role(role_name, included_by)
end
@@ -112,7 +113,7 @@ class Chef
end
def applied_role?(role_name)
- @applied_roles.has_key?(role_name)
+ @applied_roles.key?(role_name)
end
# Returns an array of role names that were expanded; this
@@ -140,18 +141,20 @@ class Chef
end
def errors
- @missing_roles_with_including_role.map { |item| item.first }
+ @missing_roles_with_including_role.map(&:first)
end
def to_json(*a)
- Chef::JSONCompat.to_json(to_hash, *a)
+ Chef::JSONCompat.to_json(to_h, *a)
end
- def to_hash
- seen_items = { :recipe => {}, :role => {} }
- { :id => @environment, :run_list => convert_run_list_trace("top level", seen_items) }
+ def to_h
+ seen_items = { recipe: {}, role: {} }
+ { id: @environment, run_list: convert_run_list_trace("top level", seen_items) }
end
+ alias_method :to_hash, :to_h
+
private
# these methods modifies internal state based on arguments, so hide it.
@@ -185,12 +188,12 @@ class Chef
seen_items[item.type][item.name] = true
case item.type
when :recipe
- { :type => "recipe", :name => item.name, :version => item.version, :skipped => !!skipped }
+ { 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 }
+ { 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
@@ -217,7 +220,7 @@ class Chef
def fetch_role(name, included_by)
Chef::Role.from_hash(rest.get("roles/#{name}"))
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => e
if e.message == '404 "Not Found"'
role_not_found(name, included_by)
else
diff --git a/lib/chef/run_list/run_list_item.rb b/lib/chef/run_list/run_list_item.rb
index 0abfb106ff..f169afc90d 100644
--- a/lib/chef/run_list/run_list_item.rb
+++ b/lib/chef/run_list/run_list_item.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,10 +18,10 @@
class Chef
class RunList
class RunListItem
- QUALIFIED_RECIPE = %r{^recipe\[([^\]@]+)(@([0-9]+(\.[0-9]+){1,2}))?\]$}
- QUALIFIED_ROLE = %r{^role\[([^\]]+)\]$}
- VERSIONED_UNQUALIFIED_RECIPE = %r{^([^@]+)(@([0-9]+(\.[0-9]+){1,2}))$}
- FALSE_FRIEND = %r{[\[\]]}
+ QUALIFIED_RECIPE = /^recipe\[([^\]@]+)(@([0-9]+(\.[0-9]+){1,2}))?\]$/.freeze
+ QUALIFIED_ROLE = /^role\[([^\]]+)\]$/.freeze
+ VERSIONED_UNQUALIFIED_RECIPE = /^([^@]+)(@([0-9]+(\.[0-9]+){1,2}))$/.freeze
+ FALSE_FRIEND = /[\[\]]/.freeze
attr_reader :name, :type, :version
@@ -32,7 +32,7 @@ class Chef
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)
+ if item.key?("version") || item.key?(:version)
@version = item["version"] || item[:version]
end
when String
@@ -80,8 +80,8 @@ class Chef
end
def ==(other)
- if other.kind_of?(String)
- self.to_s == other.to_s
+ if other.is_a?(String)
+ to_s == other.to_s
else
other.respond_to?(:type) && other.respond_to?(:name) && other.respond_to?(:version) && other.type == @type && other.name == @name && other.version == @version
end
diff --git a/lib/chef/run_list/versioned_recipe_list.rb b/lib/chef/run_list/versioned_recipe_list.rb
index 7845568aaa..f9a0a3b332 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@chef.io>)
# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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_relative "../version_class"
+require_relative "../version_constraint"
# Why does this class exist?
# Why did we not just modify RunList/RunListItem?
@@ -26,36 +26,36 @@ class Chef
def initialize
super
- @versions = Hash.new
+ @versions = {}
end
def add_recipe(name, version = nil)
- if version && @versions.has_key?(name)
+ if version && @versions.key?(name)
unless Chef::Version.new(@versions[name]) == Chef::Version.new(version)
raise Chef::Exceptions::CookbookVersionConflict, "Run list requires #{name} at versions #{@versions[name]} and #{version}"
end
end
@versions[name] = version if version
- self << name unless self.include?(name)
+ self << name unless include?(name)
end
def with_versions
- self.map { |recipe_name| { :name => recipe_name, :version => @versions[recipe_name] } }
+ map { |recipe_name| { name: recipe_name, version: @versions[recipe_name] } }
end
# Return an Array of Hashes, each of the form:
# {:name => RECIPE_NAME, :version_constraint => Chef::VersionConstraint }
def with_version_constraints
- self.map do |recipe_name|
+ map do |recipe_name|
constraint = Chef::VersionConstraint.new(@versions[recipe_name])
- { :name => recipe_name, :version_constraint => constraint }
+ { name: recipe_name, version_constraint: constraint }
end
end
# Return an Array of Strings, each of the form:
# "NAME@VERSION"
def with_version_constraints_strings
- self.map do |recipe_name|
+ map do |recipe_name|
if @versions[recipe_name]
"#{recipe_name}@#{@versions[recipe_name]}"
else
@@ -69,7 +69,7 @@ class Chef
#
# @return [Array] Array of strings with fully-qualified recipe names
def with_fully_qualified_names_and_version_constraints
- self.map do |recipe_name|
+ map do |recipe_name|
qualified_recipe = if recipe_name.include?("::")
recipe_name
else
@@ -83,17 +83,19 @@ class Chef
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
+ # For "foo::default" also include "foo", for "foo" also include "foo::default", for
+ # "foo::bar" just return "foo::bar". This makes it easier for people to search on
+ # default recipe names.
#
# @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?("::")
+ map do |recipe_name|
+ if recipe_name.end_with?("::default")
+ [ recipe_name.sub(/::default$/, ""), recipe_name ]
+ elsif recipe_name.include?("::")
recipe_name
else
- [recipe_name, "#{recipe_name}::default"]
+ [ recipe_name, "#{recipe_name}::default" ]
end
end.flatten
end
diff --git a/lib/chef/run_lock.rb b/lib/chef/run_lock.rb
index 08d58fd164..1f83b7ea5a 100644
--- a/lib/chef/run_lock.rb
+++ b/lib/chef/run_lock.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,14 +15,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require "chef/mixin/create_path"
+require_relative "mixin/create_path"
require "fcntl"
-if Chef::Platform.windows?
- require "chef/win32/mutex"
+if ChefUtils.windows?
+ require_relative "win32/mutex"
end
-require "chef/config"
-require "chef/exceptions"
-require "timeout"
+require_relative "config"
+require_relative "exceptions"
+require "timeout" unless defined?(Timeout)
+require "chef-utils" unless defined?(ChefUtils::CANARY)
class Chef
@@ -95,8 +96,8 @@ class Chef
# Waits until acquiring the system-wide lock.
#
def wait
- Chef::Log.warn("Chef client #{runpid} is running, will wait for it to finish and then run.")
- if Chef::Platform.windows?
+ Chef::Log.warn("#{ChefUtils::Dist::Infra::PRODUCT} #{runpid} is running, will wait for it to finish and then run.")
+ if ChefUtils.windows?
mutex.wait
else
runlock.flock(File::LOCK_EX)
@@ -115,7 +116,7 @@ class Chef
# Release the system-wide lock.
def release
if runlock
- if Chef::Platform.windows?
+ if ChefUtils.windows?
mutex.release
else
runlock.flock(File::LOCK_UN)
@@ -137,7 +138,7 @@ class Chef
# @api private solely for race condition tests
def acquire_lock
- if Chef::Platform.windows?
+ if ChefUtils.windows?
acquire_win32_mutex
else
# If we support FD_CLOEXEC, then use it.
@@ -172,7 +173,7 @@ class Chef
# Mutex name is case-sensitive contrary to other things in
# windows. "\" is the only invalid character.
def acquire_win32_mutex
- @mutex = Chef::ReservedNames::Win32::Mutex.new("Global\\#{runlock_file.gsub(/[\\]/, "/").downcase}")
+ @mutex = Chef::ReservedNames::Win32::Mutex.new("Global\\#{runlock_file.tr('\\', "/").downcase}")
mutex.test
end
diff --git a/lib/chef/run_status.rb b/lib/chef/run_status.rb
index c3b7945bc9..6e7c83ff48 100644
--- a/lib/chef/run_status.rb
+++ b/lib/chef/run_status.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,17 +25,13 @@ class Chef::RunStatus
attr_reader :events
- attr_reader :run_context
-
- attr_writer :run_context
+ attr_accessor :run_context
attr_reader :start_time
attr_reader :end_time
- attr_reader :exception
-
- attr_writer :exception
+ attr_accessor :exception
attr_accessor :run_id
@@ -53,6 +49,7 @@ class Chef::RunStatus
# sets +end_time+ to the current time
def stop_clock
+ @start_time ||= Time.now # if we failed so early we didn't get a start time
@end_time = Time.now
end
@@ -74,7 +71,7 @@ class Chef::RunStatus
# The list of all resources in the current run context's +resource_collection+
# that are marked as updated
def updated_resources
- @run_context && @run_context.resource_collection.select { |r| r.updated }
+ @run_context && @run_context.resource_collection.select(&:updated)
end
# The backtrace from +exception+, if any
@@ -102,20 +99,22 @@ class Chef::RunStatus
# * :updated_resources
# * :exception
# * :backtrace
- def to_hash
+ def to_h
# use a flat hash here so we can't errors from intermediate values being nil
- { :node => node,
- :success => success?,
- :start_time => start_time,
- :end_time => end_time,
- :elapsed_time => elapsed_time,
- :all_resources => all_resources,
- :updated_resources => updated_resources,
- :exception => formatted_exception,
- :backtrace => backtrace,
- :run_id => run_id }
+ { node: node,
+ success: success?,
+ start_time: start_time,
+ end_time: end_time,
+ elapsed_time: elapsed_time,
+ all_resources: all_resources,
+ updated_resources: updated_resources,
+ exception: formatted_exception,
+ backtrace: backtrace,
+ run_id: run_id }
end
+ alias_method :to_hash, :to_h
+
# Returns a string of the format "ExceptionClass: message" or +nil+ if no
# +exception+ is set.
def formatted_exception
diff --git a/lib/chef/runner.rb b/lib/chef/runner.rb
index cd5615bcec..4405843a9b 100644
--- a/lib/chef/runner.rb
+++ b/lib/chef/runner.rb
@@ -2,7 +2,7 @@
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
# Author:: Tim Hinderliter (<tim@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,10 +18,10 @@
# limitations under the License.
#
-require "chef/exceptions"
-require "chef/mixin/params_validate"
-require "chef/node"
-require "chef/resource_collection"
+require_relative "exceptions"
+require_relative "mixin/params_validate"
+require_relative "node"
+require_relative "resource_collection"
class Chef
# == Chef::Runner
@@ -34,6 +34,7 @@ class Chef
def initialize(run_context)
@run_context = run_context
+ @run_context.runner = self
end
def delayed_actions
@@ -44,6 +45,10 @@ class Chef
@run_context.events
end
+ def updated_resources
+ @run_context.updated_resources
+ end
+
# Determine the appropriate provider for the given resource, then
# execute it.
def run_action(resource, action, notification_type = nil, notifying_resource = nil)
@@ -65,36 +70,71 @@ class Chef
end
end
- # Actually run the action for realsies
+ # Actually run the action for releases
resource.run_action(action, notification_type, notifying_resource)
# Execute any immediate and queue up any delayed notifications
# associated with the resource, but only if it was updated *this time*
# we ran an action on it.
if resource.updated_by_last_action?
+ updated_resources.add(resource.declared_key) # track updated resources for unified_mode
run_context.immediate_notifications(resource).each do |notification|
- Chef::Log.info("#{resource} sending #{notification.action} action to #{notification.resource} (immediate)")
- run_action(notification.resource, notification.action, :immediate, resource)
+ if notification.resource.is_a?(String) && run_context.unified_mode
+ Chef::Log.debug("immediate notification from #{resource} to #{notification.resource} is delayed until declaration due to unified_mode")
+ else
+ Chef::Log.info("#{resource} sending #{notification.action} action to #{notification.resource} (immediate)")
+ run_action(notification.resource, notification.action, :immediate, resource)
+ end
end
run_context.delayed_notifications(resource).each do |notification|
- # send the notification to the run_context of the receiving resource
- notification.resource.run_context.add_delayed_action(notification)
+ if notification.resource.is_a?(String)
+ # for string resources that have not be declared yet in unified mode we only support notifying the current run_context
+ run_context.add_delayed_action(notification)
+ else
+ # send the notification to the run_context of the receiving resource
+ notification.resource.run_context.add_delayed_action(notification)
+ end
+ end
+ end
+ end
+
+ # Runs all of the actions on a given resource. This fires notifications and marks
+ # the resource as having been executed by the runner.
+ #
+ # @param resource [Chef::Resource] the resource to run
+ #
+ def run_all_actions(resource)
+ Array(resource.action).each { |action| run_action(resource, action) }
+ if run_context.unified_mode
+ run_context.reverse_immediate_notifications(resource).each do |n|
+ if updated_resources.include?(n.notifying_resource.declared_key)
+ n.resolve_resource_reference(run_context.resource_collection)
+ Chef::Log.info("#{resource} sent #{n.action} action to #{n.resource} (immediate at declaration time)")
+ run_action(n.resource, n.action, :immediate, n.notifying_resource)
+ end
end
end
+ ensure
+ resource.executed_by_runner = true
end
- # Iterates over the +resource_collection+ in the +run_context+ calling
- # +run_action+ for each resource in turn.
+ # Iterates over the resource_collection in the run_context calling
+ # run_action for each resource in turn.
+ #
def converge
# Resolve all lazy/forward references in notifications
- run_context.resource_collection.each do |resource|
- resource.resolve_notification_references
- end
+ run_context.resource_collection.each(&:resolve_notification_references)
# Execute each resource.
run_context.resource_collection.execute_each_resource do |resource|
- Array(resource.action).each { |action| run_action(resource, action) }
+ unless run_context.resource_collection.unified_mode
+ run_all_actions(resource)
+ end
+ end
+
+ if run_context.resource_collection.unified_mode
+ run_context.resource_collection.each { |r| r.resolve_notification_references(true) }
end
rescue Exception => e
@@ -113,7 +153,7 @@ class Chef
collected_failures.client_run_failure(error) unless error.nil?
delayed_actions.each do |notification|
result = run_delayed_notification(notification)
- if result.kind_of?(Exception)
+ if result.is_a?(Exception)
collected_failures.notification_failure(result)
end
end
@@ -123,7 +163,8 @@ class Chef
def run_delayed_notification(notification)
Chef::Log.info( "#{notification.notifying_resource} sending #{notification.action}"\
" action to #{notification.resource} (delayed)")
- # Struct of resource/action to call
+ # notifications may have lazy strings in them to resolve
+ notification.resolve_resource_reference(run_context.resource_collection)
run_action(notification.resource, notification.action, :delayed)
true
rescue Exception => e
diff --git a/lib/chef/scan_access_control.rb b/lib/chef/scan_access_control.rb
index f55a106e6d..d81166c28a 100644
--- a/lib/chef/scan_access_control.rb
+++ b/lib/chef/scan_access_control.rb
@@ -1,6 +1,6 @@
#--
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -33,7 +33,7 @@ class Chef
# Not yet sure if this is the optimal way to solve the problem. But it's
# progress towards the end goal.
#
- # TODO: figure out if all this works with OS X's negative uids
+ # TODO: figure out if all this works with macOS' negative uids
# TODO: windows
class ScanAccessControl
@@ -70,7 +70,7 @@ class Chef
when Integer
stat.uid
else
- Chef::Log.error("The `owner` parameter of the #@new_resource resource is set to an invalid value (#{new_resource.owner.inspect})")
+ Chef::Log.error("The `owner` parameter of the #{@new_resource} resource is set to an invalid value (#{new_resource.owner.inspect})")
raise ArgumentError, "cannot resolve #{new_resource.owner.inspect} to uid, owner must be a string or integer"
end
end
@@ -97,7 +97,7 @@ class Chef
when Integer
stat.gid
else
- Chef::Log.error("The `group` parameter of the #@new_resource resource is set to an invalid value (#{new_resource.owner.inspect})")
+ Chef::Log.error("The `group` parameter of the #{@new_resource} resource is set to an invalid value (#{new_resource.owner.inspect})")
raise ArgumentError, "cannot resolve #{new_resource.group.inspect} to gid, group must be a string or integer"
end
end
@@ -121,8 +121,8 @@ class Chef
when String, Integer, nil
"0#{(stat.mode & 07777).to_s(8)}"
else
- Chef::Log.error("The `mode` parameter of the #@new_resource resource is set to an invalid value (#{new_resource.mode.inspect})")
- raise ArgumentError, "Invalid value #{new_resource.mode.inspect} for `mode` on resource #@new_resource"
+ Chef::Log.error("The `mode` parameter of the #{@new_resource} resource is set to an invalid value (#{new_resource.mode.inspect})")
+ raise ArgumentError, "Invalid value #{new_resource.mode.inspect} for `mode` on resource #{@new_resource}"
end
end
diff --git a/lib/chef/search/query.rb b/lib/chef/search/query.rb
index bea8205935..c278ea9a68 100644
--- a/lib/chef/search/query.rb
+++ b/lib/chef/search/query.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/exceptions"
-require "chef/server_api"
+require_relative "../config"
+require_relative "../exceptions"
+require_relative "../server_api"
-require "uri"
-require "addressable/uri"
+autoload :URI, "uri"
+module Addressable
+ autoload :URI, "addressable/uri"
+end
class Chef
class Search
class Query
- attr_accessor :rest
attr_reader :config
def initialize(url = nil, config: Chef::Config)
@@ -39,28 +40,6 @@ class Chef
@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)
- 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
-partial search data.
-WARNDEP
-
- if !args.empty? && args.first.is_a?(Hash)
- # partial_search uses :keys instead of :filter_result for
- # result filtering.
- args_h = args.first.dup
- args_h[:filter_result] = args_h[:keys]
- args_h.delete(:keys)
-
- search(type, query, args_h, &block)
- else
- search(type, query, *args, &block)
- end
- end
-
#
# New search input, designed to be backwards compatible with the old method signature
# 'type' and 'query' are the same as before, args now will accept either a Hash of
@@ -85,6 +64,19 @@ WARNDEP
validate_type(type)
args_h = hashify_args(*args)
+ if args_h[:fuzz]
+ if type.to_sym == :node
+ query = fuzzify_node_query(query)
+ end
+ # FIXME: can i haz proper ruby-2.x named parameters someday plz?
+ args_h = args_h.reject { |k, v| k == :fuzz }
+ end
+
+ # Set default rows parameter to 1000. This is the default in
+ # Chef Server, but we set it explicitly here so that we can
+ # confidently advance our start parameter.
+ args_h[:rows] ||= 1000
+
response = call_rest_service(type, query: query, **args_h)
if block
@@ -101,7 +93,7 @@ WARNDEP
# 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)
+ next_start = response["start"] + args_h[:rows]
unless next_start >= response["total"]
args_h[:start] = next_start
search(type, query, args_h, &block)
@@ -114,8 +106,16 @@ WARNDEP
private
+ def fuzzify_node_query(query)
+ if !/:/.match?(query)
+ "tags:*#{query}* OR roles:*#{query}* OR fqdn:*#{query}* OR addresses:*#{query}* OR policy_name:*#{query}* OR policy_group:*#{query}*"
+ else
+ query
+ end
+ end
+
def validate_type(t)
- unless t.kind_of?(String) || t.kind_of?(Symbol)
+ unless t.is_a?(String) || t.is_a?(Symbol)
msg = "Invalid search object type #{t.inspect} (#{t.class}), must be a String or Symbol." +
"Usage: search(:node, QUERY[, OPTIONAL_ARGS])" +
" `knife search environment QUERY (options)`"
@@ -124,33 +124,30 @@ WARNDEP
end
def hashify_args(*args)
- return Hash.new if args.empty?
+ return {} if args.empty?
return args.first if args.first.is_a?(Hash)
- args_h = Hash.new
- args_h[:sort] = args[0] if args[0]
- args_h[:start] = args[1] if args[1]
- args_h[:rows] = args[2]
- args_h[:filter_result] = args[3]
+ args_h = {}
+ args_h[:start] = args[0] if args[0]
+ args_h[:rows] = args[1]
+ args_h[:filter_result] = args[2]
args_h
end
- QUERY_PARAM_VALUE = Addressable::URI::CharacterClasses::QUERY + "\\&\\;"
-
def escape_value(s)
- s && Addressable::URI.encode_component(s.to_s, QUERY_PARAM_VALUE)
+ query_param_value = Addressable::URI::CharacterClasses::QUERY + "\\&\\;"
+ s && Addressable::URI.encode_component(s.to_s, query_param_value)
end
- def create_query_string(type, query, rows, start, sort)
+ def create_query_string(type, query, rows, start)
qstr = "search/#{type}?q=#{escape_value(query)}"
- qstr += "&sort=#{escape_value(sort)}" if sort
qstr += "&start=#{escape_value(start)}" if start
qstr += "&rows=#{escape_value(rows)}" if rows
qstr
end
- 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)
+ def call_rest_service(type, query: "*:*", rows: nil, start: 0, filter_result: nil)
+ query_string = create_query_string(type, query, rows, start)
if filter_result
response = rest.post(query_string, filter_result)
diff --git a/lib/chef/server_api.rb b/lib/chef/server_api.rb
index cad8586ac8..7f59c33b93 100644
--- a/lib/chef/server_api.rb
+++ b/lib/chef/server_api.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,21 +16,25 @@
# 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/validate_content_length"
+require_relative "http"
+require_relative "http/authenticator"
+require_relative "http/cookie_manager"
+require_relative "http/decompressor"
+require_relative "http/json_input"
+require_relative "http/json_output"
+require_relative "http/remote_request_id"
+require_relative "http/validate_content_length"
+require_relative "http/api_versions"
class Chef
class ServerAPI < Chef::HTTP
def initialize(url = Chef::Config[:chef_server_url], options = {})
+ # # If making a change here, also update Chef::Knife::Raw::RawInputServerAPI.
options[:client_name] ||= Chef::Config[:node_name]
- options[:signing_key_filename] ||= Chef::Config[:client_key]
+ options[:raw_key] ||= Chef::Config[:client_key_contents]
+ options[:signing_key_filename] ||= Chef::Config[:client_key] unless options[:raw_key]
+ options[:ssh_agent_signing] ||= Chef::Config[:ssh_agent_signing]
options[:signing_key_filename] = nil if chef_zero_uri?(url)
options[:inflate_json_class] = false
super(url, options)
@@ -42,6 +46,7 @@ class Chef
use Chef::HTTP::Decompressor
use Chef::HTTP::Authenticator
use Chef::HTTP::RemoteRequestID
+ use Chef::HTTP::APIVersions
# ValidateContentLength should come after Decompressor
# because the order of middlewares is reversed when handling
@@ -66,13 +71,9 @@ class Chef
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"
+require_relative "config"
diff --git a/lib/chef/server_api_versions.rb b/lib/chef/server_api_versions.rb
new file mode 100644
index 0000000000..30e2802ef0
--- /dev/null
+++ b/lib/chef/server_api_versions.rb
@@ -0,0 +1,63 @@
+#--
+# Copyright:: Copyright (c) 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" unless defined?(Singleton)
+
+class Chef
+ class ServerAPIVersions
+ include Singleton
+
+ def set_versions(versions)
+ @versions ||= versions
+ end
+
+ def min_server_version
+ # If we're working with a pre-api-versioning server, always claim to be zero
+ if @versions.nil?
+ unversioned? ? 0 : nil
+ else
+ Integer(@versions["min_version"])
+ end
+ end
+
+ def max_server_version
+ # If we're working with a pre-api-versioning server, always claim to be zero
+ if @versions.nil?
+ unversioned? ? 0 : nil
+ else
+ Integer(@versions["max_version"])
+ end
+ end
+
+ def unversioned!
+ @unversioned = true
+ end
+
+ def unversioned?
+ @unversioned
+ end
+
+ def negotiated?
+ !@versions.nil? || unversioned?
+ end
+
+ def reset!
+ @versions = nil
+ @unversioned = false
+ end
+ end
+end
diff --git a/lib/chef/shell.rb b/lib/chef/shell.rb
index 26683cc25d..a425129fa8 100644
--- a/lib/chef/shell.rb
+++ b/lib/chef/shell.rb
@@ -15,21 +15,28 @@
# 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"
+module Mixlib
+ module Authentication
+ autoload :Log, "mixlib/authentication"
+ end
+end
+require "singleton" unless defined?(Singleton)
+require "pp" unless defined?(PP)
+require "etc" unless defined?(Etc)
+require "mixlib/cli" unless defined?(Mixlib::CLI)
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+require_relative "../chef"
+require_relative "version"
+require_relative "client"
+require_relative "config"
+require_relative "config_fetcher"
+
+require_relative "shell/shell_session"
+require_relative "workstation_config_loader"
+require_relative "shell/ext"
+require_relative "json_compat"
+require_relative "util/path_helper"
# = Shell
# Shell is Chef in an IRB session. Shell can interact with a Chef server via the
@@ -40,7 +47,6 @@ module Shell
LEADERS[Chef::Node] = ":attributes"
class << self
- attr_accessor :client_type
attr_accessor :options
attr_accessor :env
attr_writer :editor
@@ -60,8 +66,19 @@ module Shell
# to get access to the main object before irb starts.
::IRB.setup(nil)
+ irb_conf[:USE_COLORIZE] = options.config[:use_colorize]
+ irb_conf[:USE_SINGLELINE] = options.config[:use_singleline]
+ irb_conf[:USE_MULTILINE] = options.config[:use_multiline]
+ pp irb_conf[:USE_MULTILINE]
+
irb = IRB::Irb.new
+ if solo_mode?
+ # Setup the mocked ChefServer
+ Chef::Config.local_mode = true
+ Chef::LocalMode.setup_server_connectivity
+ end
+
init(irb.context.main)
irb_conf[:IRB_RC].call(irb.context) if irb_conf[:IRB_RC]
@@ -74,6 +91,13 @@ module Shell
catch(:IRB_EXIT) do
irb.eval_input
end
+ ensure
+ # We destroy the mocked ChefServer
+ Chef::LocalMode.destroy_server_connectivity if solo_mode?
+ end
+
+ def self.solo_mode?
+ Chef::Config[:solo]
end
def self.setup_logger
@@ -91,7 +115,7 @@ module Shell
end
# Set the irb_conf object to something other than IRB.conf
- # usful for testing.
+ # useful for testing.
def self.irb_conf=(conf_hash)
@irb_conf = conf_hash
end
@@ -107,12 +131,14 @@ module Shell
irb_conf[:IRB_RC] = lambda do |conf|
m = conf.main
- conf.prompt_c = "chef#{leader(m)} > "
+ conf.prompt_c = "#{ChefUtils::Dist::Infra::EXEC}#{leader(m)} > "
conf.return_format = " => %s \n"
- conf.prompt_i = "chef#{leader(m)} (#{Chef::VERSION})> "
- conf.prompt_n = "chef#{leader(m)} ?> "
- conf.prompt_s = "chef#{leader(m)}%l> "
+ conf.prompt_i = "#{ChefUtils::Dist::Infra::EXEC}#{leader(m)} (#{Chef::VERSION})> "
+ conf.prompt_n = "#{ChefUtils::Dist::Infra::EXEC}#{leader(m)} ?> "
+ conf.prompt_s = "#{ChefUtils::Dist::Infra::EXEC}#{leader(m)}%l> "
conf.use_tracer = false
+ conf.instance_variable_set(:@use_multiline, false)
+ conf.instance_variable_set(:@use_singleline, false)
end
end
@@ -124,6 +150,7 @@ module Shell
def self.session
unless client_type.instance.node_built?
puts "Session type: #{client_type.session_type}"
+ client_type.instance.json_configuration = @json_attribs
client_type.instance.reset!
end
client_type.instance
@@ -144,11 +171,10 @@ module Shell
puts "run `help' for help, `exit' or ^D to quit."
puts
- puts "Ohai2u#{greeting}!"
end
def self.greeting
- " #{Etc.getlogin}@#{Shell.session.node["fqdn"]}"
+ "#{Etc.getlogin}@#{Shell.session.node["fqdn"]}"
rescue NameError, ArgumentError
""
end
@@ -167,8 +193,9 @@ module Shell
def self.client_type
type = Shell::StandAloneSession
- type = Shell::SoloSession if Chef::Config[:shell_solo]
- type = Shell::ClientSession if Chef::Config[:client]
+ type = Shell::SoloSession if solo_mode?
+ type = Shell::SoloLegacySession if Chef::Config[:solo_legacy_shell]
+ type = Shell::ClientSession if Chef::Config[:client]
type = Shell::DoppelGangerSession if Chef::Config[:doppelganger]
type
end
@@ -190,85 +217,109 @@ module Shell
@footer
end
- banner("chef-shell #{Chef::VERSION}\n\nUsage: chef-shell [NAMED_CONF] (OPTIONS)")
-
- footer(<<-FOOTER)
-When no CONFIG is specified, chef-shell attempts to load a default configuration file:
-* If a NAMED_CONF is given, chef-shell will load ~/.chef/NAMED_CONF/chef_shell.rb
-* If no NAMED_CONF is given chef-shell will load ~/.chef/chef_shell.rb if it exists
-* chef-shell falls back to loading /etc/chef/client.rb or /etc/chef/solo.rb if -z or
- -s options are given and no chef_shell.rb can be found.
-FOOTER
+ banner("#{ChefUtils::Dist::Infra::SHELL} #{Chef::VERSION}\n\nUsage: #{ChefUtils::Dist::Infra::SHELL} [NAMED_CONF] (OPTIONS)")
+
+ footer(<<~FOOTER)
+ When no CONFIG is specified, #{ChefUtils::Dist::Infra::SHELL} attempts to load a default configuration file:
+ * If a NAMED_CONF is given, #{ChefUtils::Dist::Infra::SHELL} will load ~/#{ChefUtils::Dist::Infra::USER_CONF_DIR}/NAMED_CONF/#{ChefUtils::Dist::Infra::SHELL_CONF}
+ * If no NAMED_CONF is given #{ChefUtils::Dist::Infra::SHELL} will load ~/#{ChefUtils::Dist::Infra::USER_CONF_DIR}/#{ChefUtils::Dist::Infra::SHELL_CONF} if it exists
+ * If no #{ChefUtils::Dist::Infra::SHELL_CONF} can be found, #{ChefUtils::Dist::Infra::SHELL} falls back to load:
+ #{ChefConfig::Config.etc_chef_dir}/client.rb if -z option is given.
+ #{ChefConfig::Config.etc_chef_dir}/solo.rb if --solo-legacy-mode option is given.
+ #{ChefUtils::Dist::Infra::USER_CONF_DIR}/config.rb if -s option is given.
+ #{ChefUtils::Dist::Infra::USER_CONF_DIR}/knife.rb if -s option is given.
+ FOOTER
+
+ option :use_multiline,
+ long: "--[no-]multiline",
+ default: true,
+ description: "[Do not] use multiline editor module"
+
+ option :use_singleline,
+ long: "--[no-]singleline",
+ default: true,
+ description: "[Do not] use singleline editor module"
+
+ option :use_colorize,
+ long: "--[no-]colorize",
+ default: true,
+ description: "[Do not] use colorization"
option :config_file,
- :short => "-c CONFIG",
- :long => "--config CONFIG",
- :description => "The configuration file to use"
+ short: "-c CONFIG",
+ long: "--config CONFIG",
+ description: "The configuration file to use"
option :help,
- :short => "-h",
- :long => "--help",
- :description => "Show this message",
- :on => :tail,
- :boolean => true,
- :proc => proc { print_help }
+ short: "-h",
+ long: "--help",
+ description: "Show this message",
+ on: :tail,
+ boolean: true,
+ proc: proc { print_help }
option :log_level,
- :short => "-l LOG_LEVEL",
- :long => "--log-level LOG_LEVEL",
- :description => "Set the logging level",
- :proc => proc { |level| Chef::Config.log_level = level.to_sym; Shell.setup_logger }
+ short: "-l LOG_LEVEL",
+ long: "--log-level LOG_LEVEL",
+ description: "Set the logging level",
+ proc: proc { |level| Chef::Config.log_level = level.to_sym; Shell.setup_logger }
option :standalone,
- :short => "-a",
- :long => "--standalone",
- :description => "standalone session",
- :default => true,
- :boolean => true
-
- option :shell_solo,
- :short => "-s",
- :long => "--solo",
- :description => "chef-solo session",
- :boolean => true,
- :proc => proc { Chef::Config[:solo] = true }
+ short: "-a",
+ long: "--standalone",
+ description: "Standalone session",
+ default: true,
+ boolean: true
+
+ option :solo_shell,
+ short: "-s",
+ long: "--solo",
+ description: "#{ChefUtils::Dist::Solo::PRODUCT} session",
+ boolean: true,
+ proc: proc { Chef::Config[:solo] = true }
option :client,
- :short => "-z",
- :long => "--client",
- :description => "chef-client session",
- :boolean => true
+ short: "-z",
+ long: "--client",
+ description: "#{ChefUtils::Dist::Infra::PRODUCT} session",
+ boolean: true
+
+ option :solo_legacy_shell,
+ long: "--solo-legacy-mode",
+ description: "#{ChefUtils::Dist::Solo::PRODUCT} legacy session",
+ boolean: true,
+ proc: proc { Chef::Config[:solo_legacy_mode] = true }
option :json_attribs,
- :short => "-j JSON_ATTRIBS",
- :long => "--json-attributes JSON_ATTRIBS",
- :description => "Load attributes from a JSON file or URL",
- :proc => nil
+ short: "-j JSON_ATTRIBS",
+ long: "--json-attributes JSON_ATTRIBS",
+ description: "Load attributes from a JSON file or URL",
+ proc: nil
option :chef_server_url,
- :short => "-S CHEFSERVERURL",
- :long => "--server CHEFSERVERURL",
- :description => "The chef server URL",
- :proc => nil
+ short: "-S CHEFSERVERURL",
+ long: "--server CHEFSERVERURL",
+ description: "The #{ChefUtils::Dist::Server::PRODUCT} URL",
+ proc: nil
option :version,
- :short => "-v",
- :long => "--version",
- :description => "Show chef version",
- :boolean => true,
- :proc => lambda { |v| puts "Chef: #{::Chef::VERSION}" },
- :exit => 0
+ short: "-v",
+ long: "--version",
+ description: "Show #{ChefUtils::Dist::Infra::PRODUCT} version",
+ boolean: true,
+ proc: lambda { |v| puts "#{ChefUtils::Dist::Infra::PRODUCT}: #{::Chef::VERSION}" },
+ exit: 0
option :override_runlist,
- :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) } }
+ 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) } }
option :skip_cookbook_sync,
- :long => "--[no-]skip-cookbook-sync",
- :description => "Use cached cookbooks without overwriting local differences from the server",
- :boolean => false
+ long: "--[no-]skip-cookbook-sync",
+ description: "Use cached cookbooks without overwriting local differences from the server",
+ boolean: false
def self.print_help
instance = new
@@ -281,7 +332,7 @@ FOOTER
end
def self.setup!
- self.new.parse_opts
+ new.parse_opts
end
def parse_opts
@@ -293,7 +344,7 @@ FOOTER
config[:config_file] = config_file_for_shell_mode(environment)
config_msg = config[:config_file] || "none (standalone session)"
puts "loading configuration: #{config_msg}"
- Chef::Config.from_file(config[:config_file]) if !config[:config_file].nil? && File.exists?(config[:config_file]) && File.readable?(config[:config_file])
+ Chef::Config.from_file(config[:config_file]) if !config[:config_file].nil? && File.exist?(config[:config_file]) && File.readable?(config[:config_file])
Chef::Config.merge!(config)
end
@@ -305,18 +356,20 @@ FOOTER
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, ChefUtils::Dist::Infra::SHELL_CONF)
unless ::File.exist?(config_file_to_try)
- puts "could not find chef-shell config for environment #{environment} at #{config_file_to_try}"
+ puts "could not find #{ChefUtils::Dist::Infra::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 config[:solo]
- Chef::Config.platform_specific_path("/etc/chef/solo.rb")
+ elsif dot_chef_dir && ::File.exist?(File.join(dot_chef_dir, ChefUtils::Dist::Infra::SHELL_CONF))
+ File.join(dot_chef_dir, ChefUtils::Dist::Infra::SHELL_CONF)
+ elsif config[:solo_legacy_shell]
+ Chef::Config.platform_specific_path("#{ChefConfig::Config.etc_chef_dir}/solo.rb")
elsif config[:client]
- Chef::Config.platform_specific_path("/etc/chef/client.rb")
+ Chef::Config.platform_specific_path("#{ChefConfig::Config.etc_chef_dir}/client.rb")
+ elsif config[:solo_shell]
+ Chef::WorkstationConfigLoader.new(nil, Chef::Log).config_location
else
nil
end
diff --git a/lib/chef/shell/ext.rb b/lib/chef/shell/ext.rb
index 0c10309521..b884658e98 100644
--- a/lib/chef/shell/ext.rb
+++ b/lib/chef/shell/ext.rb
@@ -16,15 +16,16 @@
# 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/server_api"
-require "chef/json_compat"
+require "tempfile" unless defined?(Tempfile)
+require_relative "../recipe"
+require "fileutils" unless defined?(FileUtils)
+require_relative "../dsl/platform_introspection"
+require_relative "../version"
+require_relative "shell_session"
+require_relative "model_wrapper"
+require_relative "../server_api"
+require_relative "../json_compat"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
module Shell
module Extensions
@@ -36,7 +37,7 @@ module Shell
module ObjectCoreExtensions
def ensure_session_select_defined
- # irb breaks if you prematurely define IRB::JobMangager
+ # irb breaks if you prematurely define IRB::JobManager
# so these methods need to be defined at the latest possible time.
unless jobs.respond_to?(:select_session_by_context)
def jobs.select_session_by_context(&block) # rubocop:disable Lint/NestedMethodDefinition
@@ -46,8 +47,8 @@ module Shell
unless jobs.respond_to?(:session_select)
def jobs.select_shell_session(target_context) # rubocop:disable Lint/NestedMethodDefinition
- session = if target_context.kind_of?(Class)
- select_session_by_context { |main| main.kind_of?(target_context) }
+ session = if target_context.is_a?(Class)
+ select_session_by_context { |main| main.is_a?(target_context) }
else
select_session_by_context { |main| main.equal?(target_context) }
end
@@ -61,19 +62,19 @@ module Shell
if subsession = jobs.select_shell_session(context_obj)
jobs.switch(subsession)
else
- irb(context_obj)
+ irb(context_obj) # rubocop: disable Lint/Debugger
end
end
def help_banner
banner = []
banner << ""
- banner << "chef-shell Help"
+ banner << "#{ChefUtils::Dist::Infra::SHELL} Help"
banner << "".ljust(80, "=")
banner << "| " + "Command".ljust(25) + "| " + "Description"
banner << "".ljust(80, "=")
- self.all_help_descriptions.each do |help_text|
+ all_help_descriptions.each do |help_text|
banner << "| " + help_text.cmd.ljust(25) + "| " + help_text.desc
end
banner << "".ljust(80, "=")
@@ -84,7 +85,7 @@ module Shell
end
def explain_command(method_name)
- help = self.all_help_descriptions.find { |h| h.cmd.to_s == method_name.to_s }
+ help = all_help_descriptions.find { |h| h.cmd.to_s == method_name.to_s }
if help
puts ""
puts "Command: #{method_name}"
@@ -159,7 +160,7 @@ module Shell
module Symbol
def on_off_to_bool
- self.to_s.on_off_to_bool
+ to_s.on_off_to_bool
end
end
@@ -190,12 +191,12 @@ module Shell
extend Shell::Extensions::ObjectCoreExtensions
desc "prints this help message"
- explain(<<-E)
-## SUMMARY ##
- When called with no argument, +help+ prints a table of all
- chef-shell commands. When called with an argument COMMAND, +help+
- prints a detailed explanation of the command if available, or the
- description if no explanation is available.
+ explain(<<~E)
+ ## SUMMARY ##
+ When called with no argument, +help+ prints a table of all
+ #{ChefUtils::Dist::Infra::SHELL} commands. When called with an argument COMMAND, +help+
+ prints a detailed explanation of the command if available, or the
+ description if no explanation is available.
E
def help(commmand = nil)
if commmand
@@ -203,16 +204,14 @@ module Shell
else
puts help_banner
end
- :ucanhaz_halp
+ :help
end
alias :halp :help
- desc "prints information about chef"
+ desc "prints information about #{ChefUtils::Dist::Infra::PRODUCT}"
def version
- puts "This is the chef-shell.\n" +
- " Chef Version: #{::Chef::VERSION}\n" +
- " http://www.chef.io/\n" +
- " http://docs.chef.io/"
+ puts "Welcome to the #{ChefUtils::Dist::Infra::SHELL} #{::Chef::VERSION}\n" +
+ "For usage see https://docs.chef.io/chef_shell/"
:ucanhaz_automation
end
alias :shell :version
@@ -229,7 +228,7 @@ module Shell
:attributes
end
- desc "run chef using the current recipe"
+ desc "run #{ChefUtils::Dist::Infra::PRODUCT} using the current recipe"
def run_chef
Chef::Log.level = :debug
session = Shell.session
@@ -238,11 +237,11 @@ module Shell
runrun
end
- desc "returns an object to control a paused chef run"
- subcommands :resume => "resume the chef run",
- :step => "run only the next resource",
- :skip_back => "move back in the run list",
- :skip_forward => "move forward in the run list"
+ desc "returns an object to control a paused #{ChefUtils::Dist::Infra::PRODUCT} run"
+ subcommands resume: "resume the #{ChefUtils::Dist::Infra::PRODUCT} run",
+ step: "run only the next resource",
+ skip_back: "move back in the run list",
+ skip_forward: "move forward in the run list"
def chef_run
Shell.session.resource_collection.iterator
end
@@ -302,18 +301,18 @@ module Shell
RESTApiExtensions = Proc.new do
desc "edit an object in your EDITOR"
- explain(<<-E)
-## SUMMARY ##
- +edit(object)+ allows you to edit any object that can be converted to JSON.
- When finished editing, this method will return the edited object:
-
- new_node = edit(existing_node)
-
-## EDITOR SELECTION ##
- chef-shell looks for an editor using the following logic
- 1. Looks for an EDITOR set by Shell.editor = "EDITOR"
- 2. Looks for an EDITOR configured in your chef-shell config file
- 3. Uses the value of the EDITOR environment variable
+ explain(<<~E)
+ ## SUMMARY ##
+ +edit(object)+ allows you to edit any object that can be converted to JSON.
+ When finished editing, this method will return the edited object:
+
+ new_node = edit(existing_node)
+
+ ## EDITOR SELECTION ##
+ #{ChefUtils::Dist::Infra::SHELL} looks for an editor using the following logic
+ 1. Looks for an EDITOR set by Shell.editor = "EDITOR"
+ 2. Looks for an EDITOR configured in your #{ChefUtils::Dist::Infra::SHELL} config file
+ 3. Uses the value of the EDITOR environment variable
E
def edit(object)
unless Shell.editor
@@ -321,7 +320,7 @@ module Shell
return :failburger
end
- filename = "chef-shell-edit-#{object.class.name}-"
+ filename = "#{ChefUtils::Dist::Infra::SHELL}-edit-#{object.class.name}-"
if object.respond_to?(:name)
filename += object.name
elsif object.respond_to?(:id)
@@ -340,196 +339,196 @@ module Shell
end
desc "Find and edit API clients"
- explain(<<-E)
-## SUMMARY ##
- +clients+ allows you to query you chef server for information about your api
- clients.
+ explain(<<~E)
+ ## SUMMARY ##
+ +clients+ allows you to query you chef server for information about your api
+ clients.
-## LIST ALL CLIENTS ##
- To see all clients on the system, use
+ ## LIST ALL CLIENTS ##
+ To see all clients on the system, use
- clients.all #=> [<Chef::ApiClient...>, ...]
+ clients.all #=> [<Chef::ApiClient...>, ...]
- If the output from all is too verbose, or you're only interested in a specific
- value from each of the objects, you can give a code block to +all+:
+ If the output from all is too verbose, or you're only interested in a specific
+ value from each of the objects, you can give a code block to +all+:
- clients.all { |client| client.name } #=> [CLIENT1_NAME, CLIENT2_NAME, ...]
+ clients.all { |client| client.name } #=> [CLIENT1_NAME, CLIENT2_NAME, ...]
-## SHOW ONE CLIENT ##
- To see a specific client, use
+ ## SHOW ONE CLIENT ##
+ To see a specific client, use
- clients.show(CLIENT_NAME)
+ clients.show(CLIENT_NAME)
-## SEARCH FOR CLIENTS ##
- You can also search for clients using +find+ or +search+. You can use the
- familiar string search syntax:
+ ## SEARCH FOR CLIENTS ##
+ You can also search for clients using +find+ or +search+. You can use the
+ familiar string search syntax:
- clients.search("KEY:VALUE")
+ clients.search("KEY:VALUE")
- Just as the +all+ subcommand, the +search+ subcommand can use a code block to
- filter or transform the information returned from the search:
+ Just as the +all+ subcommand, the +search+ subcommand can use a code block to
+ filter or transform the information returned from the search:
- clients.search("KEY:VALUE") { |c| c.name }
+ clients.search("KEY:VALUE") { |c| c.name }
- You can also use a Hash based syntax, multiple search conditions will be
- joined with AND.
+ You can also use a Hash based syntax, multiple search conditions will be
+ joined with AND.
- clients.find :KEY => :VALUE, :KEY2 => :VALUE2, ...
+ clients.find :KEY => :VALUE, :KEY2 => :VALUE2, ...
-## BULK-EDIT CLIENTS ##
- **BE CAREFUL, THIS IS DESTRUCTIVE**
- You can bulk edit API Clients using the +transform+ subcommand, which requires
- a code block. Each client will be saved after the code block is run. If the
- code block returns +nil+ or +false+, that client will be skipped:
+ ## BULK-EDIT CLIENTS ##
+ **BE CAREFUL, THIS IS DESTRUCTIVE**
+ You can bulk edit API Clients using the +transform+ subcommand, which requires
+ a code block. Each client will be saved after the code block is run. If the
+ code block returns +nil+ or +false+, that client will be skipped:
- clients.transform("*:*") do |client|
- if client.name =~ /borat/i
- client.admin(false)
- true
- else
- nil
- end
- end
+ clients.transform("*:*") do |client|
+ if client.name =~ /borat/i
+ client.admin(false)
+ true
+ else
+ nil
+ end
+ end
- This will strip the admin privileges from any client named after borat.
+ This will strip the admin privileges from any client named after borat.
E
- subcommands :all => "list all api clients",
- :show => "load an api client by name",
- :search => "search for API clients",
- :transform => "edit all api clients via a code block and save them"
+ subcommands all: "list all api clients",
+ show: "load an api client by name",
+ search: "search for API clients",
+ transform: "edit all api clients via a code block and save them"
def clients
@clients ||= Shell::ModelWrapper.new(Chef::ApiClient, :client)
end
desc "Find and edit cookbooks"
- subcommands :all => "list all cookbooks",
- :show => "load a cookbook by name",
- :transform => "edit all cookbooks via a code block and save them"
+ subcommands all: "list all cookbooks",
+ show: "load a cookbook by name",
+ transform: "edit all cookbooks via a code block and save them"
def cookbooks
@cookbooks ||= Shell::ModelWrapper.new(Chef::CookbookVersion)
end
desc "Find and edit nodes via the API"
- explain(<<-E)
-## SUMMARY ##
- +nodes+ Allows you to query your chef server for information about your nodes.
+ explain(<<~E)
+ ## SUMMARY ##
+ +nodes+ Allows you to query your chef server for information about your nodes.
-## LIST ALL NODES ##
- You can list all nodes using +all+ or +list+
+ ## LIST ALL NODES ##
+ You can list all nodes using +all+ or +list+
- nodes.all #=> [<Chef::Node...>, <Chef::Node...>, ...]
+ nodes.all #=> [<Chef::Node...>, <Chef::Node...>, ...]
- To limit the information returned for each node, pass a code block to the +all+
- subcommand:
+ To limit the information returned for each node, pass a code block to the +all+
+ subcommand:
- nodes.all { |node| node.name } #=> [NODE1_NAME, NODE2_NAME, ...]
+ nodes.all { |node| node.name } #=> [NODE1_NAME, NODE2_NAME, ...]
-## SHOW ONE NODE ##
- You can show the data for a single node using the +show+ subcommand:
+ ## SHOW ONE NODE ##
+ You can show the data for a single node using the +show+ subcommand:
- nodes.show("NODE_NAME") => <Chef::Node @name="NODE_NAME" ...>
+ nodes.show("NODE_NAME") => <Chef::Node @name="NODE_NAME" ...>
-## SEARCH FOR NODES ##
- You can search for nodes using the +search+ or +find+ subcommands:
+ ## SEARCH FOR NODES ##
+ You can search for nodes using the +search+ or +find+ subcommands:
- nodes.find(:name => "app*") #=> [<Chef::Node @name="app1.example.com" ...>, ...]
+ nodes.find(:name => "app*") #=> [<Chef::Node @name="app1.example.com" ...>, ...]
- Similarly to +all+, you can pass a code block to limit or transform the
- information returned:
+ Similarly to +all+, you can pass a code block to limit or transform the
+ information returned:
- nodes.find(:name => "app#") { |node| node.ec2 }
+ nodes.find(:name => "app#") { |node| node.ec2 }
-## BULK EDIT NODES ##
- **BE CAREFUL, THIS OPERATION IS DESTRUCTIVE**
+ ## BULK EDIT NODES ##
+ **BE CAREFUL, THIS OPERATION IS DESTRUCTIVE**
- Bulk edit nodes by passing a code block to the +transform+ or +bulk_edit+
- subcommand. The block will be applied to each matching node, and then the node
- will be saved. If the block returns +nil+ or +false+, that node will be
- skipped.
+ Bulk edit nodes by passing a code block to the +transform+ or +bulk_edit+
+ subcommand. The block will be applied to each matching node, and then the node
+ will be saved. If the block returns +nil+ or +false+, that node will be
+ skipped.
- nodes.transform do |node|
- if node.fqdn =~ /.*\\.preprod\\.example\\.com/
- node.set[:environment] = "preprod"
- end
- end
+ nodes.transform do |node|
+ if node.fqdn =~ /.*\\.preprod\\.example\\.com/
+ node.set[:environment] = "preprod"
+ end
+ end
- This will assign the attribute to every node with a FQDN matching the regex.
+ This will assign the attribute to every node with a FQDN matching the regex.
E
- subcommands :all => "list all nodes",
- :show => "load a node by name",
- :search => "search for nodes",
- :transform => "edit all nodes via a code block and save them"
+ subcommands all: "list all nodes",
+ show: "load a node by name",
+ search: "search for nodes",
+ transform: "edit all nodes via a code block and save them"
def nodes
@nodes ||= Shell::ModelWrapper.new(Chef::Node)
end
desc "Find and edit roles via the API"
- explain(<<-E)
-## SUMMARY ##
- +roles+ allows you to query and edit roles on your Chef server.
-
-## SUBCOMMANDS ##
- * all (list)
- * show (load)
- * search (find)
- * transform (bulk_edit)
-
-## SEE ALSO ##
- See the help for +nodes+ for more information about the subcommands.
+ explain(<<~E)
+ ## SUMMARY ##
+ +roles+ allows you to query and edit roles on your Chef server.
+
+ ## SUBCOMMANDS ##
+ * all (list)
+ * show (load)
+ * search (find)
+ * transform (bulk_edit)
+
+ ## SEE ALSO ##
+ See the help for +nodes+ for more information about the subcommands.
E
- subcommands :all => "list all roles",
- :show => "load a role by name",
- :search => "search for roles",
- :transform => "edit all roles via a code block and save them"
+ subcommands all: "list all roles",
+ show: "load a role by name",
+ search: "search for roles",
+ transform: "edit all roles via a code block and save them"
def roles
@roles ||= Shell::ModelWrapper.new(Chef::Role)
end
desc "Find and edit +databag_name+ via the api"
- explain(<<-E)
-## SUMMARY ##
- +databags(DATABAG_NAME)+ allows you to query and edit data bag items on your
- Chef server. Unlike other commands for working with data on the server,
- +databags+ requires the databag name as an argument, for example:
- databags(:users).all
-
-## SUBCOMMANDS ##
- * all (list)
- * show (load)
- * search (find)
- * transform (bulk_edit)
-
-## SEE ALSO ##
- See the help for +nodes+ for more information about the subcommands.
+ explain(<<~E)
+ ## SUMMARY ##
+ +databags(DATABAG_NAME)+ allows you to query and edit data bag items on your
+ Chef server. Unlike other commands for working with data on the server,
+ +databags+ requires the databag name as an argument, for example:
+ databags(:users).all
+
+ ## SUBCOMMANDS ##
+ * all (list)
+ * show (load)
+ * search (find)
+ * transform (bulk_edit)
+
+ ## SEE ALSO ##
+ See the help for +nodes+ for more information about the subcommands.
E
- subcommands :all => "list all items in the data bag",
- :show => "load a data bag item by id",
- :search => "search for items in the data bag",
- :transform => "edit all items via a code block and save them"
+ subcommands all: "list all items in the data bag",
+ show: "load a data bag item by id",
+ search: "search for items in the data bag",
+ transform: "edit all items via a code block and save them"
def databags(databag_name)
@named_databags_wrappers ||= {}
@named_databags_wrappers[databag_name] ||= Shell::NamedDataBagWrapper.new(databag_name)
end
desc "Find and edit environments via the API"
- explain(<<-E)
-## SUMMARY ##
- +environments+ allows you to query and edit environments on your Chef server.
-
-## SUBCOMMANDS ##
- * all (list)
- * show (load)
- * search (find)
- * transform (bulk_edit)
-
-## SEE ALSO ##
- See the help for +nodes+ for more information about the subcommands.
+ explain(<<~E)
+ ## SUMMARY ##
+ +environments+ allows you to query and edit environments on your Chef server.
+
+ ## SUBCOMMANDS ##
+ * all (list)
+ * show (load)
+ * search (find)
+ * transform (bulk_edit)
+
+ ## SEE ALSO ##
+ See the help for +nodes+ for more information about the subcommands.
E
- subcommands :all => "list all environments",
- :show => "load an environment by name",
- :search => "search for environments",
- :transform => "edit all environments via a code block and save them"
+ subcommands all: "list all environments",
+ show: "load an environment by name",
+ search: "search for environments",
+ transform: "edit all environments via a code block and save them"
def environments
@environments ||= Shell::ModelWrapper.new(Chef::Environment)
end
diff --git a/lib/chef/shell/model_wrapper.rb b/lib/chef/shell/model_wrapper.rb
index 8c3e456a9b..78bbdb8ef6 100644
--- a/lib/chef/shell/model_wrapper.rb
+++ b/lib/chef/shell/model_wrapper.rb
@@ -1,6 +1,6 @@
#--
# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/convert_to_class_name"
-require "chef/mixin/language"
+require_relative "../mixin/convert_to_class_name"
+require_relative "../dsl/data_query"
module Shell
class ModelWrapper
@@ -33,6 +33,7 @@ module Shell
def search(query)
return all if query.to_s == "all"
+
results = []
Chef::Search::Query.new.search(@model_symbol, format_query(query)) do |obj|
if block_given?
@@ -81,7 +82,7 @@ module Shell
# the user wanted instead of the URI=>object stuff
def list_objects
objects = @model_class.method(:list).arity == 0 ? @model_class.list : @model_class.list(true)
- objects.map { |obj| Array(obj).find { |o| o.kind_of?(@model_class) } }
+ objects.map { |obj| Array(obj).find { |o| o.is_a?(@model_class) } }
end
def format_query(query)
diff --git a/lib/chef/shell/shell_session.rb b/lib/chef/shell/shell_session.rb
index a458286157..a17d8bbc84 100644
--- a/lib/chef/shell/shell_session.rb
+++ b/lib/chef/shell/shell_session.rb
@@ -2,7 +2,7 @@
# Author:: Daniel DeLeo (<dan@kallistec.com>)
# Author:: Tim Hinderliter (<tim@chef.io>)
# Copyright:: Copyright 2009-2016, Daniel DeLeo
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,16 +18,17 @@
# 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_relative "../recipe"
+require_relative "../run_context"
+require_relative "../config"
+require_relative "../client"
+require_relative "../cookbook/cookbook_collection"
+require_relative "../cookbook_loader"
+require_relative "../run_list/run_list_expansion"
+require_relative "../formatters/base"
+require_relative "../formatters/doc"
+require_relative "../formatters/minimal"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
module Shell
class ShellSession
@@ -38,8 +39,9 @@ module Shell
@session_type
end
- attr_accessor :node, :compile, :recipe, :run_context
+ attr_accessor :node, :compile, :recipe, :json_configuration
attr_reader :node_attributes, :client
+
def initialize
@node_built = false
formatter = Chef::Formatters.new(Chef::Config.formatter, STDOUT, STDERR)
@@ -73,6 +75,8 @@ module Shell
run_context.resource_collection
end
+ attr_writer :run_context
+
def run_context
@run_context ||= rebuild_context
end
@@ -86,7 +90,7 @@ module Shell
end
def save_node
- raise "Not Supported! #{self.class.name} doesn't support #save_node, maybe you need to run chef-shell in client mode?"
+ raise "Not Supported! #{self.class.name} doesn't support #save_node, maybe you need to run #{ChefUtils::Dist::Infra::SHELL} in client mode?"
end
def rebuild_context
@@ -127,7 +131,7 @@ module Shell
def shorten_node_inspect
def @node.inspect # rubocop:disable Lint/NestedMethodDefinition
- "<Chef::Node:0x#{self.object_id.to_s(16)} @name=\"#{self.name}\">"
+ "<Chef::Node:0x#{object_id.to_s(16)} @name=\"#{name}\">"
end
end
@@ -151,7 +155,7 @@ module Shell
def rebuild_node
Chef::Config[:solo_legacy_mode] = true
- @client = Chef::Client.new(nil, Chef::Config[:shell_config])
+ @client = Chef::Client.new(json_configuration, Chef::Config[:shell_config])
@client.run_ohai
@client.load_node
@client.build_node
@@ -159,9 +163,9 @@ module Shell
end
- class SoloSession < ShellSession
+ class SoloLegacySession < ShellSession
- session_type :solo
+ session_type :solo_legacy_mode
def definitions
@run_context.definitions
@@ -183,7 +187,7 @@ module Shell
def rebuild_node
# Tell the client we're chef solo so it won't try to contact the server
Chef::Config[:solo_legacy_mode] = true
- @client = Chef::Client.new(nil, Chef::Config[:shell_config])
+ @client = Chef::Client.new(json_configuration, Chef::Config[:shell_config])
@client.run_ohai
@client.load_node
@client.build_node
@@ -191,10 +195,14 @@ module Shell
end
- class ClientSession < SoloSession
+ class ClientSession < ShellSession
session_type :client
+ def definitions
+ @run_context.definitions
+ end
+
def save_node
@client.save_node
end
@@ -214,7 +222,7 @@ module Shell
def rebuild_node
# Make sure the client knows this is not chef solo
Chef::Config[:solo_legacy_mode] = false
- @client = Chef::Client.new(nil, Chef::Config[:shell_config])
+ @client = Chef::Client.new(json_configuration, Chef::Config[:shell_config])
@client.run_ohai
@client.register
@client.load_node
@@ -223,6 +231,12 @@ module Shell
end
+ class SoloSession < ClientSession
+
+ session_type :solo
+
+ end
+
class DoppelGangerClient < Chef::Client
attr_reader :node_name
@@ -241,20 +255,20 @@ module Shell
# DoppelGanger implementation of build_node. preserves as many of the node's
# attributes, and does not save updates to the server
def build_node
- Chef::Log.debug("Building node object for #{@node_name}")
+ Chef::Log.trace("Building node object for #{@node_name}")
@node = Chef::Node.find_or_create(node_name)
ohai_data = @ohai.data.merge(@node.automatic_attrs)
@node.consume_external_attrs(ohai_data, nil)
@run_list_expansion = @node.expand!("server")
@expanded_run_list_with_versions = @run_list_expansion.recipes.with_version_constraints_strings
Chef::Log.info("Run List is [#{@node.run_list}]")
- Chef::Log.info("Run List expands to [#{@expanded_run_list_with_versions.join(', ')}]")
+ Chef::Log.info("Run List expands to [#{@expanded_run_list_with_versions.join(", ")}]")
@node
end
def register
- @rest = Chef::ServerAPI.new(Chef::Config[:chef_server_url], :client_name => Chef::Config[:node_name],
- :signing_key_filename => 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
deleted file mode 100644
index 54ff718e8e..0000000000
--- a/lib/chef/shell_out.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-require "mixlib/shellout"
-
-class Chef
- class ShellOut < Mixlib::ShellOut
-
- def initialize(*args)
- Chef::Log.warn("Chef::ShellOut is deprecated, please use Mixlib::ShellOut")
- called_from = caller[0..3].inject("Called from:\n") { |msg, trace_line| msg << " #{trace_line}\n" }
- Chef::Log.warn(called_from)
- super
- end
- end
-end
diff --git a/lib/chef/tasks/chef_repo.rake b/lib/chef/tasks/chef_repo.rake
deleted file mode 100644
index 543bd8d864..0000000000
--- a/lib/chef/tasks/chef_repo.rake
+++ /dev/null
@@ -1,200 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
-# Copyright:: Copyright 2014-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.
-#
-TOPDIR = "."
-require "rake"
-
-desc "By default, print deprecation notice"
-task :default do
- puts deprecation_notice
-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 '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."
-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."
-end
-
-desc "Create a new cookbook (with COOKBOOK=name, optional CB_PREFIX=site-)"
-task :new_cookbook do
- 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
- puts "chef generate cookbook #{ENV['COOKBOOK']}"
- puts
- puts "Or, if you are not using ChefDK, use `knife cookbook create`:"
- puts
- puts "knife cookbook create #{ENV['COOKBOOK']}"
-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 '`openssl` cookbook\'s `openssl_x509` resource which can generate'
- puts "self-signed certificate chains as convergent resources."
- puts
- 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."
-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
- 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
- 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
- puts "knife role from file rolename.rb"
- puts
- puts "If you are using JSON role files, you can upload a single role with"
- puts
- puts "knife upload roles/rolename.json"
-end
-
-desc "Upload all cookbooks"
-task :upload_cookbooks do
- puts deprecation_notice
- puts deprecated_cookbook_upload
-end
-
-desc "Upload a single cookbook"
-task :upload_cookbook do
- puts deprecation_notice
- puts deprecated_cookbook_upload
-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
- 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"
-task :test_cookbook => [:test_cookbooks]
-
-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
- 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
- puts "knife upload data_bags/*"
- end
-
- desc "Create a databag"
- task :create do
- puts deprecation_notice
- puts deprecated_data_bag_creation
- end
-
- desc "Create a databag item stub"
- task :create_item do
- puts deprecation_notice
- puts deprecated_data_bag_creation
- end
-end
-
-def deprecation_notice
- %Q{*************************************************
-NOTICE: Chef Repository Rake Tasks Are Deprecated
-*************************************************
-}
-end
-
-def deprecated_cookbook_upload
- %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{
-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
-upload them. For example, if you have a data bags named `users`, with
-`finn`, and `jake` items, you would have:
-
-./data_bags/users/finn.json
-./data-bags/users/jake.json
-}
-end
diff --git a/lib/chef/resource/deploy_revision.rb b/lib/chef/train_transport.rb
index 41046ec288..4fe1fcadec 100644
--- a/lib/chef/resource/deploy_revision.rb
+++ b/lib/chef/train_transport.rb
@@ -1,6 +1,5 @@
-#
-# Author:: Daniel DeLeo (<dan@kallistec.com>)
-# Copyright:: Copyright 2009-2016, Daniel DeLeo
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,16 +15,15 @@
# limitations under the License.
#
-class Chef
- class Resource
+require "chef-config/mixin/train_transport" unless defined?(ChefConfig::Mixin::TrainTransport)
- # Convenience class for using the deploy resource with the revision
- # deployment strategy (provider)
- class DeployRevision < Chef::Resource::Deploy
- end
+class Chef
+ class TrainTransport
+ include ChefConfig::Mixin::TrainTransport
- class DeployBranch < Chef::Resource::DeployRevision
+ def config
+ require "chef/config" unless defined?(Chef::Config)
+ Chef::Config
end
-
end
end
diff --git a/lib/chef/user.rb b/lib/chef/user.rb
index a6fc21646d..e578cc2131 100644
--- a/lib/chef/user.rb
+++ b/lib/chef/user.rb
@@ -1,6 +1,6 @@
#
# Author:: Steven Danna (steve@chef.io)
-# Copyright:: Copyright 2012-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,24 +15,24 @@
# 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/server_api"
+require_relative "config"
+require_relative "mixin/params_validate"
+require_relative "mixin/from_file"
+require_relative "mash"
+require_relative "json_compat"
+require_relative "search/query"
+require_relative "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
+# corresponding 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
+# This file and corresponding osc_user knife files
# should be removed once client support for Open Source Chef Server 11 expires.
class Chef
class User
@@ -49,35 +49,35 @@ class Chef
end
def chef_rest_v0
- @chef_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], { :api_version => "0" })
+ @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\-_]+$/)
+ regex: /^[a-z0-9\-_]+$/)
end
def admin(arg = nil)
set_or_return(:admin,
- arg, :kind_of => [TrueClass, FalseClass])
+ arg, kind_of: [TrueClass, FalseClass])
end
def public_key(arg = nil)
set_or_return(:public_key,
- arg, :kind_of => String)
+ arg, kind_of: String)
end
def private_key(arg = nil)
set_or_return(:private_key,
- arg, :kind_of => String)
+ arg, kind_of: String)
end
def password(arg = nil)
set_or_return(:password,
- arg, :kind_of => String)
+ arg, kind_of: String)
end
- def to_hash
+ def to_h
result = {
"name" => @name,
"public_key" => @public_key,
@@ -88,8 +88,10 @@ class Chef
result
end
+ alias_method :to_hash, :to_h
+
def to_json(*a)
- Chef::JSONCompat.to_json(to_hash, *a)
+ Chef::JSONCompat.to_json(to_h, *a)
end
def destroy
@@ -97,34 +99,32 @@ class Chef
end
def create
- payload = { :name => self.name, :admin => self.admin, :password => self.password }
+ payload = { name: name, admin: admin, password: password }
payload[:public_key] = public_key if public_key
new_user = chef_rest_v0.post("users", payload)
- Chef::User.from_hash(self.to_hash.merge(new_user))
+ Chef::User.from_hash(to_h.merge(new_user))
end
def update(new_key = false)
- payload = { :name => name, :admin => admin }
+ payload = { name: name, admin: admin }
payload[:private_key] = new_key if new_key
payload[:password] = password if password
updated_user = chef_rest_v0.put("users/#{name}", payload)
- Chef::User.from_hash(self.to_hash.merge(updated_user))
+ Chef::User.from_hash(to_h.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
+ create
+ rescue Net::HTTPClientException => e
+ if e.response.code == "409"
+ update(new_key)
+ else
+ raise e
end
end
def reregister
- reregistered_self = chef_rest_v0.put("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
@@ -154,13 +154,8 @@ class Chef
Chef::User.from_hash(Chef::JSONCompat.from_json(json))
end
- def self.json_create(json)
- Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::User#from_json or Chef::User#load.")
- Chef::User.from_json(json)
- end
-
def self.list(inflate = false)
- response = Chef::ServerAPI.new(Chef::Config[:chef_server_url], { :api_version => "0" }).get("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
@@ -177,7 +172,7 @@ class Chef
end
def self.load(name)
- response = Chef::ServerAPI.new(Chef::Config[:chef_server_url], { :api_version => "0" }).get("users/#{name}")
+ response = Chef::ServerAPI.new(Chef::Config[:chef_server_url], { api_version: "0" }).get("users/#{name}")
Chef::User.from_hash(response)
end
@@ -186,7 +181,7 @@ class Chef
# into the form
# { "USERNAME" => "URI" }
def self.transform_ohc_list_response(response)
- new_response = Hash.new
+ new_response = {}
response.each do |u|
name = u["user"]["username"]
new_response[name] = Chef::Config[:chef_server_url] + "/users/#{name}"
diff --git a/lib/chef/user_v1.rb b/lib/chef/user_v1.rb
index db44ced9d4..945f0197df 100644
--- a/lib/chef/user_v1.rb
+++ b/lib/chef/user_v1.rb
@@ -1,6 +1,6 @@
#
# Author:: Steven Danna (steve@chef.io)
-# Copyright:: Copyright 2012-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,15 +15,15 @@
# 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"
+require_relative "config"
+require_relative "mixin/params_validate"
+require_relative "mixin/from_file"
+require_relative "mash"
+require_relative "json_compat"
+require_relative "search/query"
+require_relative "mixin/api_version_request_handling"
+require_relative "exceptions"
+require_relative "server_api"
# OSC 11 BACKWARDS COMPATIBILITY NOTE (remove after OSC 11 support ends)
#
@@ -39,7 +39,7 @@ class Chef
include Chef::Mixin::ParamsValidate
include Chef::Mixin::ApiVersionRequestHandling
- SUPPORTED_API_VERSIONS = [0, 1]
+ SUPPORTED_API_VERSIONS = [0, 1].freeze
def initialize
@username = nil
@@ -55,64 +55,64 @@ class Chef
end
def chef_root_rest_v0
- @chef_root_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_root], { :api_version => "0" })
+ @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" })
+ @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\-_]+$/)
+ regex: /^[a-z0-9\-_]+$/)
end
def display_name(arg = nil)
set_or_return(:display_name,
- arg, :kind_of => String)
+ arg, kind_of: String)
end
def first_name(arg = nil)
set_or_return(:first_name,
- arg, :kind_of => String)
+ arg, kind_of: String)
end
def middle_name(arg = nil)
set_or_return(:middle_name,
- arg, :kind_of => String)
+ arg, kind_of: String)
end
def last_name(arg = nil)
set_or_return(:last_name,
- arg, :kind_of => String)
+ arg, kind_of: String)
end
def email(arg = nil)
set_or_return(:email,
- arg, :kind_of => String)
+ arg, kind_of: String)
end
def create_key(arg = nil)
set_or_return(:create_key, arg,
- :kind_of => [TrueClass, FalseClass])
+ kind_of: [TrueClass, FalseClass])
end
def public_key(arg = nil)
set_or_return(:public_key,
- arg, :kind_of => String)
+ arg, kind_of: String)
end
def private_key(arg = nil)
set_or_return(:private_key,
- arg, :kind_of => String)
+ arg, kind_of: String)
end
def password(arg = nil)
set_or_return(:password,
- arg, :kind_of => String)
+ arg, kind_of: String)
end
- def to_hash
+ def to_h
result = {
"username" => @username,
}
@@ -128,8 +128,10 @@ class Chef
result
end
+ alias_method :to_hash, :to_h
+
def to_json(*a)
- Chef::JSONCompat.to_json(to_hash, *a)
+ Chef::JSONCompat.to_json(to_h, *a)
end
def destroy
@@ -141,17 +143,18 @@ class Chef
# 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,
+ 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
@@ -162,17 +165,18 @@ class Chef
new_user["public_key"] = new_user["chef_key"]["public_key"]
new_user.delete("chef_key")
end
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => 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,
+ 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?
@@ -180,12 +184,12 @@ class Chef
new_user = chef_root_rest_v0.post("users", payload)
end
- Chef::UserV1.from_hash(self.to_hash.merge(new_user))
+ Chef::UserV1.from_hash(to_h.merge(new_user))
end
def update(new_key = false)
begin
- payload = { :username => username }
+ 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?
@@ -198,7 +202,7 @@ class Chef
payload[:private_key] = new_key if new_key
updated_user = chef_root_rest_v1.put("users/#{username}", payload)
- rescue Net::HTTPServerException => e
+ rescue Net::HTTPClientException => 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
@@ -213,29 +217,27 @@ class Chef
end
updated_user = chef_root_rest_v0.put("users/#{username}", payload)
end
- Chef::UserV1.from_hash(self.to_hash.merge(updated_user))
+ Chef::UserV1.from_hash(to_h.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
+ create
+ rescue Net::HTTPClientException => e
+ if e.response.code == "409"
+ update(new_key)
+ else
+ raise e
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 })
+ payload = to_h.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
+ rescue Net::HTTPClientException => 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"]
@@ -276,11 +278,6 @@ class Chef
Chef::UserV1.from_hash(Chef::JSONCompat.from_json(json))
end
- def self.json_create(json)
- Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::UserV1#from_json or Chef::UserV1#load.")
- Chef::UserV1.from_json(json)
- end
-
def self.list(inflate = false)
response = Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("users")
users = if response.is_a?(Array)
@@ -316,7 +313,7 @@ class Chef
# into the form
# { "USERNAME" => "URI" }
def self.transform_list_response(response)
- new_response = Hash.new
+ new_response = {}
response.each do |u|
name = u["user"]["username"]
new_response[name] = Chef::Config[:chef_server_url] + "/users/#{name}"
diff --git a/lib/chef/util/backup.rb b/lib/chef/util/backup.rb
index 8bf2b3f25b..e739488fb9 100644
--- a/lib/chef/util/backup.rb
+++ b/lib/chef/util/backup.rb
@@ -1,6 +1,6 @@
#
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/util/path_helper"
+require_relative "path_helper"
class Chef
class Util
@@ -36,7 +36,7 @@ class Chef
slice_number = @new_resource.backup
backup_files = sorted_backup_files
if backup_files.length >= @new_resource.backup
- remainder = backup_files.slice(slice_number..-1)
+ remainder = backup_files.slice(slice_number..)
remainder.each do |backup_to_delete|
delete_backup(backup_to_delete)
end
@@ -52,7 +52,7 @@ class Chef
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
+ backup_filename = backup_filename.sub(/^([A-Za-z]:)/, "") # strip drive letter on Windows
end
end
@@ -69,7 +69,7 @@ class Chef
def do_backup
FileUtils.mkdir_p(::File.dirname(backup_path)) if Chef::Config[:file_backup_path]
- FileUtils.cp(path, backup_path, :preserve => true)
+ FileUtils.cp(path, backup_path, preserve: true)
Chef::Log.info("#{@new_resource} backed up to #{backup_path}")
end
@@ -87,7 +87,7 @@ class Chef
end
def sorted_backup_files
- unsorted_backup_files.sort { |a, b| b <=> a }
+ unsorted_backup_files.sort.reverse # faster than sort { |a, b| b <=> a }
end
end
end
diff --git a/lib/chef/util/diff.rb b/lib/chef/util/diff.rb
index bb1b4e2b95..0774dea813 100644
--- a/lib/chef/util/diff.rb
+++ b/lib/chef/util/diff.rb
@@ -1,5 +1,5 @@
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -40,15 +40,11 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OF OTHER DEALINGS IN THE
# SOFTWARE.
-require "diff/lcs"
-require "diff/lcs/hunk"
-
class Chef
class Util
class Diff
# @todo: to_a, to_s, to_json, inspect defs, accessors for @diff and @error
# @todo: move coercion to UTF-8 into to_json
- # @todo: replace shellout to diff -u with diff-lcs gem
def for_output
# formatted output to a terminal uses arrays of strings and returns error strings
@@ -58,13 +54,14 @@ class Chef
def for_reporting
# caller needs to ensure that new files aren't posted to resource reporting
return nil if @diff.nil?
+
@diff.join("\\n")
end
def use_tempfile_if_missing(file)
tempfile = nil
- unless File.exists?(file)
- Chef::Log.debug("File #{file} does not exist to diff against, using empty tempfile")
+ unless File.exist?(file)
+ Chef::Log.trace("File #{file} does not exist to diff against, using empty tempfile")
tempfile = Tempfile.new("chef-diff")
file = tempfile.path
end
@@ -86,11 +83,14 @@ class Chef
# produces a unified-output-format diff with 3 lines of context
# ChefFS uses udiff() directly
def udiff(old_file, new_file)
+ require "diff/lcs"
+ require "diff/lcs/hunk"
+
diff_str = ""
file_length_difference = 0
- old_data = IO.readlines(old_file).map { |e| e.chomp }
- new_data = IO.readlines(new_file).map { |e| e.chomp }
+ old_data = IO.readlines(old_file).map(&:chomp)
+ new_data = IO.readlines(new_file).map(&:chomp)
diff_data = ::Diff::LCS.diff(old_data, new_data)
return diff_str if old_data.empty? && new_data.empty?
@@ -106,18 +106,19 @@ class Chef
# join them. otherwise, print out the old one.
old_hunk = hunk = nil
diff_data.each do |piece|
- begin
- hunk = ::Diff::LCS::Hunk.new(old_data, new_data, piece, 3, file_length_difference)
- file_length_difference = hunk.file_length_difference
- next unless old_hunk
- next if hunk.merge(old_hunk)
- diff_str << old_hunk.diff(:unified) << "\n"
- ensure
- old_hunk = hunk
- end
+
+ hunk = ::Diff::LCS::Hunk.new(old_data, new_data, piece, 3, file_length_difference)
+ file_length_difference = hunk.file_length_difference
+ next unless old_hunk
+ next if hunk.merge(old_hunk)
+
+ diff_str << old_hunk.diff(:unified) << "\n"
+ ensure
+ old_hunk = hunk
+
end
diff_str << old_hunk.diff(:unified) << "\n"
- return diff_str
+ diff_str
end
private
@@ -134,12 +135,12 @@ class Chef
return "(file sizes exceed #{diff_filesize_threshold} bytes, diff output suppressed)"
end
- # MacOSX(BSD?) diff will *sometimes* happily spit out nasty binary diffs
+ # macOS(BSD?) diff will *sometimes* happily spit out nasty binary diffs
return "(current file is binary, diff output suppressed)" if is_binary?(old_file)
return "(new content is binary, diff output suppressed)" if is_binary?(new_file)
begin
- Chef::Log.debug("Running: diff -u #{old_file} #{new_file}")
+ Chef::Log.trace("Running: diff -u #{old_file} #{new_file}")
diff_str = udiff(old_file, new_file)
rescue Exception => e
@@ -150,14 +151,14 @@ class Chef
if !diff_str.empty? && diff_str != "No differences encountered\n"
if diff_str.length > diff_output_threshold
- return "(long diff of over #{diff_output_threshold} characters, diff output suppressed)"
+ "(long diff of over #{diff_output_threshold} characters, diff output suppressed)"
else
diff_str = encode_diff_for_json(diff_str)
@diff = diff_str.split("\n")
- return "(diff available)"
+ "(diff available)"
end
else
- return "(no diff)"
+ "(no diff)"
end
end
@@ -169,14 +170,15 @@ class Chef
begin
return buff !~ /\A[\s[:print:]]*\z/m
rescue ArgumentError => e
- return true if e.message =~ /invalid byte sequence/
+ return true if /invalid byte sequence/.match?(e.message)
+
raise
end
end
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 8b492d483a..7785dc3990 100644
--- a/lib/chef/util/dsc/configuration_generator.rb
+++ b/lib/chef/util/dsc/configuration_generator.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Edwards (<adamed@chef.io>)
#
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,38 +16,37 @@
# limitations under the License.
#
-require "chef/util/powershell/cmdlet"
+require_relative "../../mixin/powershell_exec"
class Chef::Util::DSC
class ConfigurationGenerator
+ include Chef::Mixin::PowershellExec
+
def initialize(node, config_directory)
@node = node
@config_directory = config_directory
end
- def configuration_document_from_script_code(code, configuration_flags, imports, shellout_flags)
- Chef::Log.debug("DSC: DSC code:\n '#{code}'")
+ def configuration_document_from_script_code(code, configuration_flags, imports)
+ Chef::Log.trace("DSC: DSC code:\n '#{code}'")
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)
ensure
::FileUtils.rm(generated_script_path)
end
end
- def configuration_document_from_script_path(script_path, configuration_name, configuration_flags, shellout_flags)
+ def configuration_document_from_script_path(script_path, configuration_name, configuration_flags)
validate_configuration_name!(configuration_name)
- document_generation_cmdlet = Chef::Util::Powershell::Cmdlet.new(
- @node,
- configuration_document_generation_code(script_path, configuration_name))
-
- merged_configuration_flags = get_merged_configuration_flags!(configuration_flags, configuration_name)
+ config_generation_code = configuration_document_generation_code(script_path, configuration_name)
+ switches_string = command_switches_string(get_merged_configuration_flags!(configuration_flags, configuration_name))
- document_generation_cmdlet.run!(merged_configuration_flags, shellout_flags)
+ powershell_exec!("#{config_generation_code} #{switches_string}")
configuration_document_location = find_configuration_document(configuration_name)
- if ! configuration_document_location
+ unless configuration_document_location
raise "No DSC configuration for '#{configuration_name}' was generated from supplied DSC script"
end
@@ -58,6 +57,50 @@ class Chef::Util::DSC
protected
+ def validate_switch_name!(switch_parameter_name)
+ unless switch_parameter_name.match?(/\A[A-Za-z]+[_a-zA-Z0-9]*\Z/)
+ raise ArgumentError, "`#{switch_parameter_name}` is not a valid PowerShell cmdlet switch parameter name"
+ end
+ end
+
+ def escape_parameter_value(parameter_value)
+ parameter_value.gsub(/(`|'|"|#)/, '`\1')
+ end
+
+ def escape_string_parameter_value(parameter_value)
+ "'#{escape_parameter_value(parameter_value)}'"
+ end
+
+ 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}'. The switch must be specified as a Symbol'"
+ end
+
+ validate_switch_name!(switch_name)
+
+ switch_argument = ""
+ switch_present = true
+
+ case switch_value
+ when Numeric, Float
+ switch_argument = switch_value.to_s
+ when FalseClass
+ switch_present = false
+ when TrueClass
+ # nothing
+ 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}`. 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 : ""
+ end
+
+ command_switches.join(" ")
+ end
+
# From PowerShell error help for the Configuration language element:
# Standard names may only contain letters (a-z, A-Z), numbers (0-9), and underscore (_).
# The name may not be null or empty, and should start with a letter.
@@ -68,12 +111,13 @@ class Chef::Util::DSC
end
def get_merged_configuration_flags!(configuration_flags, configuration_name)
- merged_configuration_flags = { :outputpath => configuration_document_directory(configuration_name) }
+ merged_configuration_flags = { outputpath: configuration_document_directory(configuration_name) }
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} that is disallowed."
end
+
merged_configuration_flags[switch.to_s.downcase.to_sym] = value
end
end
@@ -81,16 +125,16 @@ class Chef::Util::DSC
end
def configuration_code(code, configuration_name, imports)
- <<-EOF
-$ProgressPreference = 'SilentlyContinue';
-Configuration '#{configuration_name}'
-{
- #{generate_import_resource_statements(imports).join(" \n")}
- node 'localhost'
- {
- #{code}
- }
-}
+ <<~EOF
+ $ProgressPreference = 'SilentlyContinue';
+ Configuration '#{configuration_name}'
+ {
+ #{generate_import_resource_statements(imports).join(" \n")}
+ node 'localhost'
+ {
+ #{code}
+ }
+ }
EOF
end
@@ -100,7 +144,7 @@ Configuration '#{configuration_name}'
if resources.length == 0 || resources.include?("*")
"Import-DscResource -ModuleName #{resource_module}"
else
- "Import-DscResource -ModuleName #{resource_module} -Name #{resources.join(',')}"
+ "Import-DscResource -ModuleName #{resource_module} -Name #{resources.join(",")}"
end
end
else
@@ -131,9 +175,7 @@ Configuration '#{configuration_name}'
end
def get_configuration_document(document_path)
- ::File.open(document_path, "rb") do |file|
- file.read
- end
+ ::File.open(document_path, "rb", &:read)
end
end
end
diff --git a/lib/chef/util/dsc/lcm_output_parser.rb b/lib/chef/util/dsc/lcm_output_parser.rb
index bdcedff7f8..d05ea3ba68 100644
--- a/lib/chef/util/dsc/lcm_output_parser.rb
+++ b/lib/chef/util/dsc/lcm_output_parser.rb
@@ -1,7 +1,7 @@
#
# Author:: Jay Mundrawala (<jdm@chef.io>)
#
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/log"
-require "chef/util/dsc/resource_info"
-require "chef/exceptions"
+require_relative "../../log"
+require_relative "resource_info"
+require_relative "../../exceptions"
class Chef
class Util
@@ -28,7 +28,7 @@ class Chef
# Parses the output from LCM and returns a list of Chef::Util::DSC::ResourceInfo objects
# that describe how the resources affected the system
#
- # Example:
+ # Example for WhatIfParser:
# parse <<-EOF
# What if: [Machine]: LCM: [Start Set ]
# What if: [Machine]: LCM: [Start Resource ] [[File]FileToNotBeThere]
@@ -53,12 +53,64 @@ class Chef
# )
# ]
#
- def self.parse(lcm_output)
- lcm_output ||= ""
- current_resource = Hash.new
+ # Example for TestDSCParser:
+ # parse <<-EOF
+ # InDesiredState : False
+ # ResourcesInDesiredState :
+ # ResourcesNotInDesiredState: {[Environment]texteditor}
+ # ReturnValue : 0
+ # PSComputerName : .
+ # EOF
+ #
+ # would return
+ #
+ # [
+ # Chef::Util::DSC::ResourceInfo.new(
+ # '{[Environment]texteditor}',
+ # true,
+ # [
+ # ]
+ # )
+ # ]
+ #
+
+ def self.parse(lcm_output, test_dsc_configuration)
+ lcm_output = String(lcm_output).split("\n")
+ test_dsc_configuration ? test_dsc_parser(lcm_output) : what_if_parser(lcm_output)
+ end
+
+ def self.test_dsc_parser(lcm_output)
+ current_resource = {}
+
+ resources = []
+ lcm_output.each do |line|
+ op_action , op_value = line.strip.split(":")
+ op_action&.strip!
+ case op_action
+ when "InDesiredState"
+ current_resource[:skipped] = op_value.strip == "True" ? true : false
+ when "ResourcesInDesiredState", "ResourcesNotInDesiredState"
+ current_resource[:name] = op_value.strip if op_value
+ when "ReturnValue"
+ current_resource[:context] = nil
+ end
+ end
+ if current_resource[:name]
+ resources.push(current_resource)
+ end
+
+ if resources.length > 0
+ build_resource_info(resources)
+ else
+ raise Chef::Exceptions::LCMParser, "Could not parse:\n#{lcm_output}"
+ end
+ end
+
+ def self.what_if_parser(lcm_output)
+ current_resource = {}
resources = []
- lcm_output.lines.each do |line|
+ lcm_output.each do |line|
op_action, op_type, info = parse_line(line)
case op_action
@@ -73,9 +125,9 @@ class Chef
if current_resource[:name]
resources.push(current_resource)
end
- current_resource = { :name => info }
+ current_resource = { name: info }
else
- Chef::Log.debug("Ignoring op_action #{op_action}: Read line #{line}")
+ Chef::Log.trace("Ignoring op_action #{op_action}: Read line #{line}")
end
when :end
# Make sure we log the last line
@@ -105,9 +157,9 @@ class Chef
def self.parse_line(line)
if match = line.match(/^.*?:.*?:\s*LCM:\s*\[(.*?)\](.*)/)
- # If the line looks like
- # What If: [machinename]: LCM: [op_action op_type] message
- # extract op_action, op_type, and message
+ # If the line looks like
+ # 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 }
else
@@ -119,7 +171,7 @@ class Chef
end
end
info.strip! # Because this was formatted for humans
- return [op_action, op_type, info]
+ [op_action, op_type, info]
end
private_class_method :parse_line
diff --git a/lib/chef/util/dsc/local_configuration_manager.rb b/lib/chef/util/dsc/local_configuration_manager.rb
index 741c6a5898..c0f9c72da8 100644
--- a/lib/chef/util/dsc/local_configuration_manager.rb
+++ b/lib/chef/util/dsc/local_configuration_manager.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Edwards (<adamed@chef.io>)
#
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,25 +16,27 @@
# limitations under the License.
#
-require "chef/util/powershell/cmdlet"
-require "chef/util/dsc/lcm_output_parser"
+require_relative "../../mixin/powershell_exec"
+require_relative "lcm_output_parser"
class Chef::Util::DSC
class LocalConfigurationManager
+ include Chef::Mixin::PowershellExec
+
def initialize(node, configuration_path)
@node = node
@configuration_path = configuration_path
clear_execution_time
end
- def test_configuration(configuration_document, shellout_flags)
- status = run_configuration_cmdlet(configuration_document, false, shellout_flags)
- log_what_if_exception(status.stderr) unless status.succeeded?
- configuration_update_required?(status.return_value)
+ def test_configuration(configuration_document)
+ status = run_configuration_cmdlet(configuration_document, false)
+ log_dsc_exception(status.errors.join("\n")) if status.error?
+ configuration_update_required?(status.result)
end
- def set_configuration(configuration_document, shellout_flags)
- run_configuration_cmdlet(configuration_document, true, shellout_flags)
+ def set_configuration(configuration_document)
+ run_configuration_cmdlet(configuration_document, true)
end
def last_operation_execution_time_seconds
@@ -45,64 +47,75 @@ class Chef::Util::DSC
private
- 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 }" : ""
+ def run_configuration_cmdlet(configuration_document, apply_configuration)
+ Chef::Log.trace("DSC: Calling DSC Local Config Manager to #{apply_configuration ? "set" : "test"} configuration document.")
start_operation_timing
- command_code = lcm_command_code(@configuration_path, test_only_parameters)
status = nil
begin
save_configuration_document(configuration_document)
- cmdlet = ::Chef::Util::Powershell::Cmdlet.new(@node, "#{command_code}")
+ cmd = lcm_command(apply_configuration)
+ Chef::Log.trace("DSC: Calling DSC Local Config Manager with:\n#{cmd}")
+
+ status = powershell_exec(cmd)
if apply_configuration
- status = cmdlet.run!({}, shellout_flags)
- else
- status = cmdlet.run({}, shellout_flags)
+ status.error!
end
ensure
end_operation_timing
remove_configuration_document
if last_operation_execution_time_seconds
- Chef::Log.debug("DSC: DSC operation completed in #{last_operation_execution_time_seconds} seconds.")
+ Chef::Log.trace("DSC: DSC operation completed in #{last_operation_execution_time_seconds} seconds.")
end
end
- Chef::Log.debug("DSC: Completed call to DSC Local Config Manager")
+ Chef::Log.trace("DSC: Completed call to DSC Local Config Manager")
status
end
- def lcm_command_code(configuration_path, test_only_parameters)
- <<-EOH
-$ProgressPreference = 'SilentlyContinue';start-dscconfiguration -path #{@configuration_path} -wait -erroraction 'continue' -force #{test_only_parameters}
-EOH
+ def lcm_command(apply_configuration)
+ common_command_prefix = "$ProgressPreference = 'SilentlyContinue';"
+ ps4_base_command = "#{common_command_prefix} Start-DscConfiguration -path #{@configuration_path} -wait -erroraction 'stop' -force"
+ if apply_configuration
+ ps4_base_command
+ else
+ if ps_version_gte_5?
+ "#{common_command_prefix} Test-DscConfiguration -path #{@configuration_path} | format-list | Out-String"
+ else
+ ps4_base_command + " -whatif; if (! $?) { exit 1 }"
+ end
+ end
+ end
+
+ def ps_version_gte_5?
+ Chef::Platform.supported_powershell_version?(@node, 5)
end
- def log_what_if_exception(what_if_exception_output)
- if whatif_not_supported?(what_if_exception_output)
- # LCM returns an error if any of the resources do not support the opptional What-If
+ def log_dsc_exception(dsc_exception_output)
+ if whatif_not_supported?(dsc_exception_output)
+ # LCM returns an error if any of the resources do not support the optional What-If
Chef::Log.warn("Received error while testing configuration due to resource not supporting 'WhatIf'")
- elsif dsc_module_import_failure?(what_if_exception_output)
- Chef::Log.warn("Received error while testing configuration due to a module for an imported resource possibly not being fully installed:\n#{what_if_exception_output.gsub(/\s+/, ' ')}")
+ elsif dsc_module_import_failure?(dsc_exception_output)
+ Chef::Log.warn("Received error while testing configuration due to a module for an imported resource possibly not being fully installed:\n#{dsc_exception_output.gsub(/\s+/, " ")}")
else
- Chef::Log.warn("Received error while testing configuration:\n#{what_if_exception_output.gsub(/\s+/, ' ')}")
+ Chef::Log.warn("Received error while testing configuration:\n#{dsc_exception_output.gsub(/\s+/, " ")}")
end
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)
+ def whatif_not_supported?(dsc_exception_output)
+ !! (dsc_exception_output.gsub(/\n+/, "").gsub(/\s+/, " ") =~ /A parameter cannot be found that matches parameter name 'Whatif'/i)
end
- def dsc_module_import_failure?(what_if_output)
- !! (what_if_output =~ /\sCimException/ &&
- what_if_output =~ /ProviderOperationExecutionFailure/ &&
- what_if_output =~ /\smodule\s+is\s+installed/)
+ def dsc_module_import_failure?(command_output)
+ !! (command_output =~ /\sCimException/ &&
+ command_output.include?("ProviderOperationExecutionFailure") &&
+ command_output =~ /\smodule\s+is\s+installed/)
end
- def configuration_update_required?(what_if_output)
- Chef::Log.debug("DSC: DSC returned the following '-whatif' output from test operation:\n#{what_if_output}")
+ def configuration_update_required?(command_output)
+ Chef::Log.trace("DSC: DSC returned the following '-whatif' output from test operation:\n#{command_output}")
begin
- Parser.parse(what_if_output)
+ Parser.parse(command_output, ps_version_gte_5?)
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."])]
diff --git a/lib/chef/util/dsc/resource_store.rb b/lib/chef/util/dsc/resource_store.rb
index be8d0b301b..49ca46832a 100644
--- a/lib/chef/util/dsc/resource_store.rb
+++ b/lib/chef/util/dsc/resource_store.rb
@@ -1,7 +1,7 @@
#
# Author:: Jay Mundrawala (<jdm@chef.io>)
#
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,14 +16,14 @@
# limitations under the License.
#
-require "chef/util/powershell/cmdlet"
-require "chef/util/powershell/cmdlet_result"
-require "chef/exceptions"
+require_relative "../../mixin/powershell_exec"
+require_relative "../../exceptions"
class Chef
class Util
class DSC
class ResourceStore
+ include Chef::Mixin::PowershellExec
def self.instance
@@instance ||= ResourceStore.new.tap do |store|
@@ -74,7 +74,7 @@ class Chef
found = rs.find_all do |r|
name_matches = r["Name"].casecmp(name) == 0
if name_matches
- module_name == nil || (r["Module"] && r["Module"]["Name"].casecmp(module_name) == 0)
+ module_name.nil? || (r["Module"] && r["Module"]["Name"].casecmp(module_name) == 0)
else
false
end
@@ -83,19 +83,13 @@ class Chef
# Returns a list of dsc resources
def query_resources
- cmdlet = Chef::Util::Powershell::Cmdlet.new(nil, "get-dscresource",
- :object)
- result = cmdlet.run
- result.return_value
+ powershell_exec("get-dscresource").result
end
# Returns a list of dsc resources matching the provided name
def query_resource(resource_name)
- cmdlet = Chef::Util::Powershell::Cmdlet.new(nil, "get-dscresource #{resource_name}",
- :object)
- result = cmdlet.run
- ret_val = result.return_value
- if ret_val.nil?
+ ret_val = powershell_exec("get-dscresource #{resource_name}").result
+ if ret_val.empty?
[]
elsif ret_val.is_a? Array
ret_val
diff --git a/lib/chef/util/editor.rb b/lib/chef/util/editor.rb
index fa4f0ec12e..4b6e08fe74 100644
--- a/lib/chef/util/editor.rb
+++ b/lib/chef/util/editor.rb
@@ -1,6 +1,6 @@
#
# Author:: Chris Bandy (<bandy.chris@gmail.com>)
-# Copyright:: Copyright 2014-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/lib/chef/util/file_edit.rb b/lib/chef/util/file_edit.rb
index 5aa33fd169..0cd1f4dce1 100644
--- a/lib/chef/util/file_edit.rb
+++ b/lib/chef/util/file_edit.rb
@@ -1,6 +1,6 @@
#
# Author:: Nuo Yan (<nuo@chef.io>)
-# Copyright:: Copyright 2009-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -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_relative "editor"
+require "fileutils" unless defined?(FileUtils)
class Chef
class Util
@@ -30,6 +30,7 @@ class Chef
def initialize(filepath)
raise ArgumentError, "File '#{filepath}' does not exist" unless File.exist?(filepath)
+
@editor = Editor.new(File.open(filepath, &:readlines))
@original_pathname = filepath
@file_edited = false
@@ -40,38 +41,38 @@ class Chef
@file_edited
end
- #search the file line by line and match each line with the given regex
- #if matched, replace the whole line with newline.
+ # search the file line by line and match each line with the given regex
+ # if matched, replace the whole line with newline.
def search_file_replace_line(regex, newline)
@changes = (editor.replace_lines(regex, newline) > 0) || @changes
end
- #search the file line by line and match each line with the given regex
- #if matched, replace the match (all occurrences) with the replace parameter
+ # search the file line by line and match each line with the given regex
+ # if matched, replace the match (all occurrences) with the replace parameter
def search_file_replace(regex, replace)
@changes = (editor.replace(regex, replace) > 0) || @changes
end
- #search the file line by line and match each line with the given regex
- #if matched, delete the line
+ # search the file line by line and match each line with the given regex
+ # if matched, delete the line
def search_file_delete_line(regex)
@changes = (editor.remove_lines(regex) > 0) || @changes
end
- #search the file line by line and match each line with the given regex
- #if matched, delete the match (all occurrences) from the line
+ # 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, "")
end
- #search the file line by line and match each line with the given regex
- #if matched, insert newline after each matching line
+ # search the file line by line and match each line with the given regex
+ # if matched, insert newline after each matching line
def insert_line_after_match(regex, newline)
@changes = (editor.append_line_after(regex, newline) > 0) || @changes
end
- #search the file line by line and match each line with the given regex
- #if not matched, insert newline at the end of the file
+ # search the file line by line and match each line with the given regex
+ # if not matched, insert newline at the end of the file
def insert_line_if_no_match(regex, newline)
@changes = (editor.append_line_if_missing(regex, newline) > 0) || @changes
end
@@ -80,11 +81,11 @@ class Chef
!!@changes
end
- #Make a copy of old_file and write new file out (only if file changed)
+ # Make a copy of old_file and write new file out (only if file changed)
def write_file
if @changes
backup_pathname = original_pathname + ".old"
- FileUtils.cp(original_pathname, backup_pathname, :preserve => true)
+ FileUtils.cp(original_pathname, backup_pathname, preserve: true)
File.open(original_pathname, "w") do |newfile|
editor.lines.each do |line|
newfile.puts(line)
diff --git a/lib/chef/util/path_helper.rb b/lib/chef/util/path_helper.rb
index 6389458b6a..0eb3f59b49 100644
--- a/lib/chef/util/path_helper.rb
+++ b/lib/chef/util/path_helper.rb
@@ -1,6 +1,6 @@
#
# Author:: Bryan McLellan <btm@loftninjas.org>
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/lib/chef/util/powershell/cmdlet.rb b/lib/chef/util/powershell/cmdlet.rb
deleted file mode 100644
index e300266b1e..0000000000
--- a/lib/chef/util/powershell/cmdlet.rb
+++ /dev/null
@@ -1,173 +0,0 @@
-#
-# Author:: Adam Edwards (<adamed@chef.io>)
-#
-# Copyright:: Copyright 2014-2016, 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 "mixlib/shellout"
-require "chef/mixin/windows_architecture_helper"
-require "chef/util/powershell/cmdlet_result"
-
-class Chef
- class Util
- class Powershell
- class Cmdlet
- def initialize(node, cmdlet, output_format = nil, output_format_options = {})
- @output_format = output_format
- @node = node
-
- case output_format
- when nil
- @json_format = false
- when :json
- @json_format = true
- when :text
- @json_format = false
- when :object
- @json_format = true
- else
- raise ArgumentError, "Invalid output format #{output_format} specified"
- end
-
- @cmdlet = cmdlet
- @output_format_options = output_format_options
- end
-
- attr_reader :output_format
-
- def run(switches = {}, execution_options = {}, *arguments)
- streams = { :json => CmdletStream.new("json"),
- :verbose => CmdletStream.new("verbose"),
- }
-
- arguments_string = arguments.join(" ")
-
- switches_string = command_switches_string(switches)
-
- json_depth = 5
-
- if @json_format && @output_format_options.has_key?(:depth)
- json_depth = @output_format_options[:depth]
- end
-
- json_command = if @json_format
- " | convertto-json -compress -depth #{json_depth} > #{streams[:json].path}"
- else
- ""
- end
- redirections = "4> '#{streams[:verbose].path}'"
- command_string = "powershell.exe -executionpolicy bypass -noprofile -noninteractive "\
- "-command \"trap [Exception] {write-error -exception "\
- "($_.Exception.Message);exit 1};#{@cmdlet} #{switches_string} "\
- "#{arguments_string} #{redirections}"\
- "#{json_command}\";if ( ! $? ) { exit 1 }"
-
- augmented_options = { :returns => [0], :live_stream => false }.merge(execution_options)
- command = Mixlib::ShellOut.new(command_string, augmented_options)
-
- status = nil
-
- with_os_architecture(@node) do
- status = command.run_command
- end
-
- CmdletResult.new(status, streams, @output_format)
- end
-
- def run!(switches = {}, execution_options = {}, *arguments)
- result = run(switches, execution_options, arguments)
-
- if ! result.succeeded?
- raise Chef::Exceptions::PowershellCmdletException, "Powershell Cmdlet failed: #{result.stderr}"
- end
-
- result
- end
-
- protected
-
- include Chef::Mixin::WindowsArchitectureHelper
-
- def validate_switch_name!(switch_parameter_name)
- if !!(switch_parameter_name =~ /\A[A-Za-z]+[_a-zA-Z0-9]*\Z/) == false
- raise ArgumentError, "`#{switch_parameter_name}` is not a valid PowerShell cmdlet switch parameter name"
- end
- end
-
- def escape_parameter_value(parameter_value)
- parameter_value.gsub(/(`|'|"|#)/, '`\1')
- end
-
- def escape_string_parameter_value(parameter_value)
- "'#{escape_parameter_value(parameter_value)}'"
- end
-
- 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}'. The switch must be specified as a Symbol'"
- end
-
- validate_switch_name!(switch_name)
-
- switch_argument = ""
- switch_present = true
-
- case switch_value
- when Numeric
- switch_argument = switch_value.to_s
- when Float
- switch_argument = switch_value.to_s
- when FalseClass
- switch_present = false
- when TrueClass
- 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}`. 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 : ""
- end
-
- command_switches.join(" ")
- end
-
- class CmdletStream
- def initialize(name)
- @filename = Dir::Tmpname.create(name) {}
- ObjectSpace.define_finalizer(self, self.class.destroy(@filename))
- end
-
- def path
- @filename
- end
-
- def read
- if File.exist? @filename
- File.open(@filename, "rb:bom|UTF-16LE") do |f|
- f.read.encode("UTF-8")
- end
- end
- end
-
- def self.destroy(name)
- proc { File.delete(name) if File.exists? name }
- end
- end
- end
- end
- end
-end
diff --git a/lib/chef/util/powershell/cmdlet_result.rb b/lib/chef/util/powershell/cmdlet_result.rb
deleted file mode 100644
index 82aef4da40..0000000000
--- a/lib/chef/util/powershell/cmdlet_result.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-#
-# Author:: Adam Edwards (<adamed@chef.io>)
-#
-# Copyright:: Copyright 2014-2016, 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/json_compat"
-
-class Chef
- class Util
- class Powershell
- class CmdletResult
- attr_reader :output_format
-
- def initialize(status, streams, output_format)
- @status = status
- @output_format = output_format
- @streams = streams
- end
-
- def stdout
- @status.stdout
- end
-
- def stderr
- @status.stderr
- end
-
- def stream(name)
- @streams[name].read
- end
-
- def return_value
- if output_format == :object
- Chef::JSONCompat.parse(stream(:json))
- elsif output_format == :json
- stream(:json)
- else
- @status.stdout
- end
- end
-
- def succeeded?
- @succeeded = @status.status.exitstatus == 0
- end
- end
- end
- end
-end
diff --git a/lib/chef/util/powershell/ps_credential.rb b/lib/chef/util/powershell/ps_credential.rb
index 32810b98a6..0404f5a1ac 100644
--- a/lib/chef/util/powershell/ps_credential.rb
+++ b/lib/chef/util/powershell/ps_credential.rb
@@ -1,7 +1,7 @@
#
# Author:: Jay Mundrawala (<jdm@chef.io>)
#
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,26 +16,30 @@
# limitations under the License.
#
-require "chef/win32/crypto" if Chef::Platform.windows?
+require_relative "../../win32/crypto" if ChefUtils.windows?
-class Chef::Util::Powershell
- class PSCredential
- def initialize(username, password)
- @username = username
- @password = password
- end
+class Chef
+ class Util
+ class Powershell
+ class PSCredential
+ def initialize(username, password)
+ @username = username
+ @password = password
+ end
- def to_psobject
- "New-Object System.Management.Automation.PSCredential('#{@username}',('#{encrypt(@password)}' | ConvertTo-SecureString))"
- end
+ def to_psobject
+ "New-Object System.Management.Automation.PSCredential('#{@username}',('#{encrypt(@password)}' | ConvertTo-SecureString))"
+ end
- alias to_s to_psobject
- alias to_text to_psobject
+ alias to_s to_psobject
+ alias to_text to_psobject
- private
+ private
- def encrypt(str)
- Chef::ReservedNames::Win32::Crypto.encrypt(str)
+ def encrypt(str)
+ Chef::ReservedNames::Win32::Crypto.encrypt(str)
+ end
+ end
end
end
end
diff --git a/lib/chef/util/selinux.rb b/lib/chef/util/selinux.rb
index edca589034..8016262b6f 100644
--- a/lib/chef/util/selinux.rb
+++ b/lib/chef/util/selinux.rb
@@ -3,7 +3,7 @@
# Author:: Kevin Keane
# Author:: Lamont Granquist (<lamont@chef.io>)
#
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# Copyright:: Copyright 2013-2016, North County Tech Center, LLC
#
# License:: Apache License, Version 2.0
@@ -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_relative "../mixin/shell_out"
+require_relative "../mixin/which"
class Chef
class Util
@@ -48,10 +48,11 @@ class Chef
def restore_security_context(file_path, recursive = false)
if restorecon_path
- restorecon_command = recursive ? "#{restorecon_path} -R -r" : "#{restorecon_path} -R"
- restorecon_command += " \"#{file_path}\""
- Chef::Log.debug("Restoring selinux security content with #{restorecon_command}")
- shell_out!(restorecon_command)
+ restorecon_flags = [ "-R" ]
+ restorecon_flags << "-r" if recursive
+ restorecon_flags << file_path
+ Chef::Log.trace("Restoring selinux security content with #{restorecon_path}")
+ shell_out!(restorecon_path, restorecon_flags)
else
Chef::Log.warn "Can not find 'restorecon' on the system. Skipping selinux security context restore."
end
@@ -71,19 +72,19 @@ class Chef
def check_selinux_enabled?
if selinuxenabled_path
- cmd = shell_out!(selinuxenabled_path, :returns => [0, 1])
+ cmd = shell_out!(selinuxenabled_path, returns: [0, 1])
case cmd.exitstatus
when 1
- return false
+ false
when 0
- return true
+ true
else
raise "Unknown exit code from command #{selinuxenabled_path}: #{cmd.exitstatus}"
end
else
# We assume selinux is not enabled if selinux utils are not
# installed.
- return false
+ false
end
end
diff --git a/lib/chef/util/threaded_job_queue.rb b/lib/chef/util/threaded_job_queue.rb
index eaffd9ea70..bce25e9225 100644
--- a/lib/chef/util/threaded_job_queue.rb
+++ b/lib/chef/util/threaded_job_queue.rb
@@ -1,4 +1,4 @@
-# Copyright:: Copyright 2014-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,8 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require "thread"
-
class Chef
class Util
# A simple threaded job queue
@@ -54,7 +52,7 @@ class Chef
end
end
workers.each { |worker| self << Thread.method(:exit) }
- workers.each { |worker| worker.join }
+ workers.each(&:join)
end
end
end
diff --git a/lib/chef/util/windows/logon_session.rb b/lib/chef/util/windows/logon_session.rb
new file mode 100644
index 0000000000..b29f24565c
--- /dev/null
+++ b/lib/chef/util/windows/logon_session.rb
@@ -0,0 +1,129 @@
+#
+# Author:: Adam Edwards (<adamed@chef.io>)
+#
+# Copyright:: Copyright (c) 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_relative "../../win32/api/security" if ChefUtils.windows?
+require_relative "../../mixin/wide_string"
+
+class Chef
+ class Util
+ class Windows
+ class LogonSession
+ include Chef::Mixin::WideString
+
+ def initialize(username, password, domain = nil, authentication = :remote)
+ if username.nil? || password.nil?
+ raise ArgumentError, "The logon session must be initialize with non-nil user name and password parameters"
+ end
+
+ @original_username = username
+ @original_password = password
+ @original_domain = domain
+ @authentication = authentication
+ @token = FFI::Buffer.new(:pointer)
+ @session_opened = false
+ @impersonating = false
+ end
+
+ def open
+ if session_opened
+ raise "Attempted to open a logon session that was already open."
+ end
+
+ username = wstring(original_username)
+ password = wstring(original_password)
+ domain = wstring(original_domain)
+
+ logon_type = (authentication == :local) ? (Chef::ReservedNames::Win32::API::Security::LOGON32_LOGON_NETWORK) : (Chef::ReservedNames::Win32::API::Security::LOGON32_LOGON_NEW_CREDENTIALS)
+ status = Chef::ReservedNames::Win32::API::Security.LogonUserW(username, domain, password, logon_type, Chef::ReservedNames::Win32::API::Security::LOGON32_PROVIDER_DEFAULT, token)
+
+ unless status
+ last_error = FFI::LastError.error
+ raise Chef::Exceptions::Win32APIError, "Logon for user `#{original_username}` failed with Win32 status #{last_error}."
+ end
+
+ @session_opened = true
+ end
+
+ def close
+ validate_session_open!
+
+ if impersonating
+ restore_user_context
+ end
+
+ Chef::ReservedNames::Win32::API::System.CloseHandle(token.read_ulong)
+ @token = nil
+ @session_opened = false
+ end
+
+ def set_user_context
+ validate_session_open!
+
+ unless session_opened
+ raise "Attempted to set the user context before opening a session."
+ end
+
+ if impersonating
+ raise "Attempt to set the user context when the user context is already set."
+ end
+
+ status = Chef::ReservedNames::Win32::API::Security.ImpersonateLoggedOnUser(token.read_ulong)
+
+ unless status
+ last_error = FFI::LastError.error
+ raise Chef::Exceptions::Win32APIError, "Attempt to impersonate user `#{original_username}` failed with Win32 status #{last_error}."
+ end
+
+ @impersonating = true
+ end
+
+ def restore_user_context
+ validate_session_open!
+
+ if impersonating
+ status = Chef::ReservedNames::Win32::API::Security.RevertToSelf
+
+ unless status
+ last_error = FFI::LastError.error
+ raise Chef::Exceptions::Win32APIError, "Unable to restore user context with Win32 status #{last_error}."
+ end
+ end
+
+ @impersonating = false
+ end
+
+ protected
+
+ attr_reader :original_username
+ attr_reader :original_password
+ attr_reader :original_domain
+ attr_reader :authentication
+
+ attr_reader :token
+ attr_reader :session_opened
+ attr_reader :impersonating
+
+ def validate_session_open!
+ unless session_opened
+ raise "Attempted to set the user context before opening a session."
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/util/windows/net_group.rb b/lib/chef/util/windows/net_group.rb
index 0a351fbc6f..a762faa5bd 100644
--- a/lib/chef/util/windows/net_group.rb
+++ b/lib/chef/util/windows/net_group.rb
@@ -16,10 +16,10 @@
# limitations under the License.
#
-require "chef/util/windows"
-require "chef/win32/net"
+require_relative "../windows"
+require_relative "../../win32/net"
-#wrapper around a subset of the NetGroup* APIs.
+# wrapper around a subset of the NetGroup* APIs.
class Chef::Util::Windows::NetGroup
private
@@ -35,50 +35,44 @@ class Chef::Util::Windows::NetGroup
end
def local_get_members
- begin
- Chef::ReservedNames::Win32::NetUser.net_local_group_get_members(nil, groupname)
- rescue Chef::Exceptions::Win32APIError => e
- raise ArgumentError, e
- end
+ Chef::ReservedNames::Win32::NetUser.net_local_group_get_members(nil, groupname)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
def local_add
- begin
- Chef::ReservedNames::Win32::NetUser.net_local_group_add(nil, groupname)
- rescue Chef::Exceptions::Win32APIError => e
- raise ArgumentError, e
- end
+ Chef::ReservedNames::Win32::NetUser.net_local_group_add(nil, groupname)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
def local_set_members(members)
- begin
- Chef::ReservedNames::Win32::NetUser.net_local_group_set_members(nil, groupname, members)
- rescue Chef::Exceptions::Win32APIError => e
- raise ArgumentError, e
- end
+ Chef::ReservedNames::Win32::NetUser.net_local_group_set_members(nil, groupname, members)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
def local_add_members(members)
- begin
- Chef::ReservedNames::Win32::NetUser.net_local_group_add_members(nil, groupname, members)
- rescue Chef::Exceptions::Win32APIError => e
- raise ArgumentError, e
- end
+ Chef::ReservedNames::Win32::NetUser.net_local_group_add_members(nil, groupname, members)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
+ end
+
+ def local_group_set_info(comment)
+ Chef::ReservedNames::Win32::NetUser.net_local_group_set_info(nil, groupname, comment)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
def local_delete_members(members)
- begin
- Chef::ReservedNames::Win32::NetUser.net_local_group_del_members(nil, groupname, members)
- rescue Chef::Exceptions::Win32APIError => e
- raise ArgumentError, e
- end
+ Chef::ReservedNames::Win32::NetUser.net_local_group_del_members(nil, groupname, members)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
def local_delete
- begin
- Chef::ReservedNames::Win32::NetUser.net_local_group_del(nil, groupname)
- rescue Chef::Exceptions::Win32APIError => e
- raise ArgumentError, e
- end
+ Chef::ReservedNames::Win32::NetUser.net_local_group_del(nil, groupname)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
end
diff --git a/lib/chef/util/windows/net_use.rb b/lib/chef/util/windows/net_use.rb
index b9c3ecc783..9e6735429b 100644
--- a/lib/chef/util/windows/net_use.rb
+++ b/lib/chef/util/windows/net_use.rb
@@ -16,12 +16,12 @@
# limitations under the License.
#
-#the Win32 Volume APIs do not support mapping network drives. not supported by WMI either.
-#see also: WNetAddConnection2 and WNetAddConnection3
-#see also cmd.exe: net use /?
+# the Win32 Volume APIs do not support mapping network drives. not supported by WMI either.
+# see also: WNetAddConnection2 and WNetAddConnection3
+# see also cmd.exe: net use /?
-require "chef/util/windows"
-require "chef/win32/net"
+require_relative "../windows"
+require_relative "../../win32/net"
class Chef::Util::Windows::NetUse < Chef::Util::Windows
def initialize(localname)
@@ -38,7 +38,7 @@ class Chef::Util::Windows::NetUse < Chef::Util::Windows
def add(args)
if args.class == String
remote = args
- args = Hash.new
+ args = {}
args[:remote] = remote
end
args[:local] ||= use_name
@@ -59,24 +59,20 @@ class Chef::Util::Windows::NetUse < Chef::Util::Windows
end
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
+ 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
def device
- get_info()[:remote]
+ get_info[:remote]
end
def delete
- begin
- Chef::ReservedNames::Win32::Net.net_use_del(nil, use_name, :use_noforce)
- rescue Chef::Exceptions::Win32APIError => e
- raise ArgumentError, e
- end
+ Chef::ReservedNames::Win32::Net.net_use_del(nil, use_name, :use_noforce)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
def use_name
diff --git a/lib/chef/util/windows/net_user.rb b/lib/chef/util/windows/net_user.rb
index 009252c4c1..b6767c41c5 100644
--- a/lib/chef/util/windows/net_user.rb
+++ b/lib/chef/util/windows/net_user.rb
@@ -16,19 +16,20 @@
# limitations under the License.
#
-require "chef/util/windows"
-require "chef/exceptions"
-require "chef/win32/net"
-require "chef/win32/security"
+require_relative "../windows"
+require_relative "../../exceptions"
+require_relative "../../win32/net"
+require_relative "../../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.
+# 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
NetUser = Chef::ReservedNames::Win32::NetUser
Security = Chef::ReservedNames::Win32::Security
+ Win32APIError = Chef::ReservedNames::Win32::API::Error
USER_INFO_3_TRANSFORM = {
name: :usri3_name,
@@ -60,7 +61,7 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
profile: :usri3_profile,
home_dir_drive: :usri3_home_dir_drive,
password_expired: :usri3_password_expired,
- }
+ }.freeze
def transform_usri3(args)
args.inject({}) do |memo, (k, v)|
@@ -78,11 +79,9 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
end
def set_info(args)
- begin
- rc = NetUser.net_user_set_info_l3(nil, @username, transform_usri3(args))
- rescue Chef::Exceptions::Win32APIError => e
- raise ArgumentError, e
- end
+ rc = NetUser.net_user_set_info_l3(nil, @username, transform_usri3(args))
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
public
@@ -93,15 +92,21 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
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
+ # XXX for an extra painful alternative, see: http://support.microsoft.com/kb/180548
def validate_credentials(passwd)
- begin
- token = Security.logon_user(@username, nil, passwd,
- LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT)
- return true
- rescue Chef::Exceptions::Win32APIError
+ token = Security.logon_user(@username, nil, passwd,
+ LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT)
+ true
+ rescue Chef::Exceptions::Win32APIError => e
+ Chef::Log.trace(e)
+ # we're only interested in the incorrect password failures
+ if /System Error Code: 1326/.match?(e.to_s)
return false
end
+
+ # all other exceptions will assume we cannot logon for a different reason
+ Chef::Log.trace("Unable to login with the specified credentials. Assuming the credentials are valid.")
+ true
end
def get_info
@@ -116,14 +121,14 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
def add(args)
transformed_args = transform_usri3(args)
NetUser.net_user_add_l3(nil, transformed_args)
- NetUser.net_local_group_add_member(nil, "Users", args[:name])
+ NetUser.net_local_group_add_member(nil, Chef::ReservedNames::Win32::Security::SID.BuiltinUsers.account_simple_name, args[:name])
end
# FIXME: yard with @yield
def user_modify
user = get_info
- user[:last_logon] = user[:units_per_week] = 0 #ignored as per USER_INFO_3 doc
- user[:logon_hours] = nil #PBYTE field; \0 == no changes
+ user[:last_logon] = user[:units_per_week] = 0 # ignored as per USER_INFO_3 doc
+ user[:logon_hours] = nil # PBYTE field; \0 == no changes
yield(user)
set_info(user)
end
@@ -137,19 +142,17 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
end
def delete
- begin
- NetUser.net_user_del(nil, @username)
- rescue Chef::Exceptions::Win32APIError => e
- raise ArgumentError, e
- end
+ NetUser.net_user_del(nil, @username)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
def disable_account
user_modify do |user|
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
+ # This does not set the password to nil. It (for some reason) means to ignore updating the field.
+ # See similar behavior for the logon_hours field documented at
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa371338%28v=vs.85%29.aspx
user[:password] = nil
end
end
@@ -157,14 +160,14 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
def enable_account
user_modify do |user|
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
+ # This does not set the password to nil. It (for some reason) means to ignore updating the field.
+ # See similar behavior for the logon_hours field documented at
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa371338%28v=vs.85%29.aspx
user[:password] = nil
end
end
def check_enabled
- (get_info()[:flags] & NetUser::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 a18644cece..e197604c34 100644
--- a/lib/chef/util/windows/volume.rb
+++ b/lib/chef/util/windows/volume.rb
@@ -16,44 +16,34 @@
# limitations under the License.
#
-#simple wrapper around Volume APIs. might be possible with WMI, but possibly more complex.
+# simple wrapper around Volume APIs. might be possible with WMI, but possibly more complex.
-require "chef/win32/api/file"
-require "chef/util/windows"
+require_relative "../../win32/api/file"
+require_relative "../windows"
class Chef::Util::Windows::Volume < Chef::Util::Windows
attr_reader :mount_point
def initialize(name)
- name += "\\" unless name =~ /\\$/ #trailing slash required
+ name += "\\" unless /\\$/.match?(name) # trailing slash required
@mount_point = name
end
def device
- begin
- Chef::ReservedNames::Win32::File.get_volume_name_for_volume_mount_point(mount_point)
- rescue Chef::Exceptions::Win32APIError => e
- raise ArgumentError, e
- end
+ Chef::ReservedNames::Win32::File.get_volume_name_for_volume_mount_point(mount_point)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
def delete
- begin
- Chef::ReservedNames::Win32::File.delete_volume_mount_point(mount_point)
- rescue Chef::Exceptions::Win32APIError => e
- raise ArgumentError, e
- end
+ Chef::ReservedNames::Win32::File.delete_volume_mount_point(mount_point)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
def add(args)
- 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
+ Chef::ReservedNames::Win32::File.set_volume_mount_point(mount_point, args[:remote])
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
end
diff --git a/lib/chef/version.rb b/lib/chef/version.rb
index 947cd9c0b2..d8b7a74674 100644
--- a/lib/chef/version.rb
+++ b/lib/chef/version.rb
@@ -1,4 +1,4 @@
-# Copyright:: Copyright 2010-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,15 +13,17 @@
# 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.
-#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+require_relative "version_string"
class Chef
- CHEF_ROOT = File.expand_path("../..", __FILE__)
- VERSION = "12.14.75"
+ CHEF_ROOT = File.expand_path("..", __dir__)
+ VERSION = Chef::VersionString.new("17.0.32")
end
#
diff --git a/lib/chef/version/platform.rb b/lib/chef/version/platform.rb
index 07b1a17b11..83e2a4570a 100644
--- a/lib/chef/version/platform.rb
+++ b/lib/chef/version/platform.rb
@@ -14,8 +14,26 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require "chef/version_class"
+require_relative "../version_class"
+# NOTE: this is fairly badly broken for its purpose and should not be used
+# unless it gets fixed.
+
+# this strictly wants x, x.y, or x.y.z version constraints in the target and
+# will fail hard if it does not match. the semantics that we need here is that
+# it must always do the best job that it can do and consume as much of the
+# offered version as it can. since we accept arbitrarily parsed strings into
+# node[:platform_version] out of dozens or potentially hundreds of operating
+# systems this parsing code needs to be fixed to never raise. the Gem::Version
+# class is a better model, and in fact it might be a substantially better approach
+# to base this class on Gem::Version and then do pre-mangling of things like windows
+# version strings via e.g. `.gsub(/R/, '.')`. the raising behavior of this parser
+# however, breaks the ProviderResolver in a not just buggy but a "completely unfit
+# for purpose" way.
+#
+# TL;DR: MUST follow the second part of "Be conservative in what you send,
+# be liberal in what you accept"
+#
class Chef
class Version
class Platform < Chef::Version
diff --git a/lib/chef/version_class.rb b/lib/chef/version_class.rb
index f26368902d..4f3102281f 100644
--- a/lib/chef/version_class.rb
+++ b/lib/chef/version_class.rb
@@ -1,6 +1,6 @@
# Author:: Seth Falcon (<seth@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -33,8 +33,8 @@ class Chef
end
def <=>(other)
- [:major, :minor, :patch].each do |method|
- version = self.send(method)
+ %i{major minor patch}.each do |method|
+ version = send(method)
begin
ans = (version <=> other.send(method))
rescue NoMethodError # if the other thing isn't a version object, return nil
diff --git a/lib/chef/version_constraint.rb b/lib/chef/version_constraint.rb
index f10325f946..0abbbb49b5 100644
--- a/lib/chef/version_constraint.rb
+++ b/lib/chef/version_constraint.rb
@@ -1,6 +1,6 @@
# Author:: Seth Falcon (<seth@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
-# Copyright:: Copyright 2010-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,14 +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.
-require "chef/version_class"
+require_relative "version_class"
class Chef
class VersionConstraint
- DEFAULT_CONSTRAINT = ">= 0.0.0"
- STANDARD_OPS = %w{< > <= >=}
- OPS = %w{< > = <= >= ~>}
- PATTERN = /^(#{OPS.join('|')}) *([0-9].*)$/
+ DEFAULT_CONSTRAINT = ">= 0.0.0".freeze
+ STANDARD_OPS = %w{< > <= >=}.freeze
+ OPS = %w{< > = <= >= ~>}.freeze
+ PATTERN = /^(#{OPS.join('|')}) *([0-9].*)$/.freeze
VERSION_CLASS = Chef::Version
attr_reader :op, :version
@@ -90,7 +90,7 @@ class Chef
parse(constraint_spec.first)
else
msg = "only one version constraint operation is supported, but you gave #{constraint_spec.size} "
- msg << "['#{constraint_spec.join(', ')}']"
+ msg << "['#{constraint_spec.join(", ")}']"
raise Chef::Exceptions::InvalidVersionConstraint, msg
end
end
diff --git a/lib/chef/version_constraint/platform.rb b/lib/chef/version_constraint/platform.rb
index 29f4678bb5..0c04d61c8f 100644
--- a/lib/chef/version_constraint/platform.rb
+++ b/lib/chef/version_constraint/platform.rb
@@ -13,9 +13,11 @@
# WITHOUT WARRANTIES OR 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_relative "../version_constraint"
+require_relative "../version/platform"
+# NOTE: this is fairly badly broken for its purpose and should not be used
+# unless it gets fixed. see chef/version/platform.
class Chef
class VersionConstraint
class Platform < Chef::VersionConstraint
diff --git a/lib/chef/resource/timestamped_deploy.rb b/lib/chef/version_string.rb
index 1d6b07a719..8da5df570a 100644
--- a/lib/chef/resource/timestamped_deploy.rb
+++ b/lib/chef/version_string.rb
@@ -1,6 +1,4 @@
-#
-# Author:: Daniel DeLeo (<dan@kallistec.com>)
-# Copyright:: Copyright 2009-2016, Daniel DeLeo
+# Copyright:: Copyright 2017, Noah Kantrowitz
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,13 +12,9 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
+
+require "chef-utils/version_string"
class Chef
- class Resource
- # Convenience class for using the deploy resource with the timestamped
- # deployment strategy (provider)
- class TimestampedDeploy < Chef::Resource::Deploy
- end
- end
+ VersionString = ChefUtils::VersionString
end
diff --git a/lib/chef/win32/api.rb b/lib/chef/win32/api.rb
index 64db9d2b63..957823220d 100644
--- a/lib/chef/win32/api.rb
+++ b/lib/chef/win32/api.rb
@@ -1,7 +1,7 @@
#
# Author:: Seth Chisamore (<schisamo@chef.io>)
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,9 +17,9 @@
# limitations under the License.
#
-require "ffi"
-require "chef/reserved_names"
-require "chef/exceptions"
+require "ffi" unless defined?(FFI)
+require_relative "../reserved_names"
+require_relative "../exceptions"
class Chef
module ReservedNames::Win32
@@ -29,12 +29,10 @@ class Chef
# function into the calling module. If this fails a dummy method is
# defined which when called, raises a helpful exception to the end-user.
def safe_attach_function(win32_func, *args)
- begin
- attach_function(win32_func.to_sym, *args)
- rescue FFI::NotFoundError
- define_method(win32_func.to_sym) do |*margs|
- raise Chef::Exceptions::Win32APIFunctionNotImplemented, "This version of Windows does not implement the Win32 function [#{win32_func}]."
- end
+ attach_function(win32_func.to_sym, *args)
+ rescue FFI::NotFoundError
+ define_method(win32_func.to_sym) do |*margs|
+ raise Chef::Exceptions::Win32APIFunctionNotImplemented, "This version of Windows does not implement the Win32 function [#{win32_func}]."
end
end
@@ -53,7 +51,7 @@ class Chef
host.typedef :bool, :BOOL
host.typedef :bool, :BOOLEAN
host.typedef :uchar, :BYTE # Byte (8 bits). Declared as unsigned char
- #CALLBACK: K, # Win32.API gem-specific ?? MSDN: #define CALLBACK __stdcall
+ # CALLBACK: K, # Win32.API gem-specific ?? MSDN: #define CALLBACK __stdcall
host.typedef :char, :CHAR # 8-bit Windows (ANSI) character. See http://msdn.microsoft.com/en-us/library/dd183415%28VS.85%29.aspx
host.typedef :uint32, :COLORREF # Red, green, blue (RGB) color value (32 bits). See COLORREF for more info.
host.typedef :uint32, :DWORD # 32-bit unsigned integer. The range is 0 through 4,294,967,295 decimal.
@@ -80,7 +78,7 @@ class Chef
host.typedef :ulong, :HDESK # (L) Handle to a desktop. http://msdn.microsoft.com/en-us/library/ms682573%28VS.85%29.aspx
host.typedef :ulong, :HDROP # (L) Handle to an internal drop structure.
host.typedef :ulong, :HDWP # (L) Handle to a deferred window position structure.
- host.typedef :ulong, :HENHMETAFILE #(L) Handle to an enhanced metafile. http://msdn.microsoft.com/en-us/library/dd145051%28VS.85%29.aspx
+ host.typedef :ulong, :HENHMETAFILE # (L) Handle to an enhanced metafile. http://msdn.microsoft.com/en-us/library/dd145051%28VS.85%29.aspx
host.typedef :uint, :HFILE # (I) Special file handle to a file opened by OpenFile, not CreateFile.
# WinDef.h: #host.typedef int HFILE;
host.typedef :ulong, :HFONT # (L) Handle to a font. http://msdn.microsoft.com/en-us/library/dd162470%28VS.85%29.aspx
@@ -96,7 +94,7 @@ class Chef
host.typedef :ulong, :HMENU # (L) Handle to a menu. http://msdn.microsoft.com/en-us/library/ms646977%28VS.85%29.aspx
host.typedef :ulong, :HMETAFILE # (L) Handle to a metafile. http://msdn.microsoft.com/en-us/library/dd145051%28VS.85%29.aspx
host.typedef :ulong, :HMODULE # (L) Handle to an instance. Same as HINSTANCE today, but was different in 16-bit Windows.
- host.typedef :ulong, :HMONITOR # (L) Рandle to a display monitor. WinDef.h: if(WINVER >= 0x0500) host.typedef HANDLE HMONITOR;
+ host.typedef :ulong, :HMONITOR # (L) Handle to a display monitor. WinDef.h: if(WINVER >= 0x0500) host.typedef HANDLE HMONITOR;
host.typedef :ulong, :HPALETTE # (L) Handle to a palette.
host.typedef :ulong, :HPEN # (L) Handle to a pen. http://msdn.microsoft.com/en-us/library/dd162786%28VS.85%29.aspx
host.typedef :long, :HRESULT # Return code used by COM interfaces. For more info, Structure of the COM Error Codes.
@@ -109,7 +107,7 @@ class Chef
host.typedef :int, :INT # 32-bit signed integer. The range is -2147483648 through 2147483647 decimal.
host.typedef :int, :INT_PTR # Signed integer type for pointer precision. Use when casting a pointer to an integer
# to perform pointer arithmetic. BaseTsd.h:
- #if defined(_WIN64) host.typedef __int64 INT_PTR; #else host.typedef int INT_PTR;
+ # if defined(_WIN64) host.typedef __int64 INT_PTR; #else host.typedef int INT_PTR;
host.typedef :int32, :INT32 # 32-bit signed integer. The range is -2,147,483,648 through +...647 decimal.
host.typedef :int64, :INT64 # 64-bit signed integer. The range is –9,223,372,036,854,775,808 through +...807
host.typedef :ushort, :LANGID # Language identifier. For more information, see Locales. WinNT.h: #host.typedef WORD LANGID;
@@ -117,14 +115,14 @@ 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 :pointer, :LMSTR # Pointer to null terminated 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
host.typedef :int64, :LONGLONG # 64-bit signed integer. The range is –9,223,372,036,854,775,808 through +...807
host.typedef :long, :LONG_PTR # Signed long type for pointer precision. Use when casting a pointer to a long to
# perform pointer arithmetic. BaseTsd.h:
- #if defined(_WIN64) host.typedef __int64 LONG_PTR; #else host.typedef long LONG_PTR;
+ # if defined(_WIN64) host.typedef __int64 LONG_PTR; #else host.typedef long LONG_PTR;
host.typedef :long, :LPARAM # Message parameter. WinDef.h as follows: #host.typedef LONG_PTR LPARAM;
host.typedef :pointer, :LPBOOL # Pointer to a BOOL. WinDef.h as follows: #host.typedef BOOL far *LPBOOL;
host.typedef :pointer, :LPBYTE # Pointer to a BYTE. WinDef.h as follows: #host.typedef BYTE far *LPBYTE;
@@ -162,7 +160,7 @@ class Chef
host.typedef :pointer, :PDWORD32 # Pointer to a DWORD32.
host.typedef :pointer, :PDWORD64 # Pointer to a DWORD64.
host.typedef :pointer, :PFLOAT # Pointer to a FLOAT.
- host.typedef :pointer, :PGENERICMAPPING #Pointer to GENERIC_MAPPING
+ host.typedef :pointer, :PGENERICMAPPING # Pointer to GENERIC_MAPPING
host.typedef :pointer, :PHALF_PTR # Pointer to a HALF_PTR.
host.typedef :pointer, :PHANDLE # Pointer to a HANDLE.
host.typedef :pointer, :PHKEY # Pointer to an HKEY.
@@ -237,7 +235,7 @@ class Chef
host.typedef :ulong_long, :USN # Update sequence number (USN).
host.typedef :ushort, :WCHAR # 16-bit Unicode character. For more information, see Character Sets Used By Fonts.
# In WinNT.h: host.typedef wchar_t WCHAR;
- #WINAPI: K, # Calling convention for system functions. WinDef.h: define WINAPI __stdcall
+ # WINAPI: K, # Calling convention for system functions. WinDef.h: define WINAPI __stdcall
host.typedef :ushort, :WORD # 16-bit unsigned integer. The range is 0 through 65535 decimal.
host.typedef :uint, :WPARAM # Message parameter. WinDef.h as follows: host.typedef UINT_PTR WPARAM;
end
diff --git a/lib/chef/win32/api/command_line_helper.rb b/lib/chef/win32/api/command_line_helper.rb
new file mode 100644
index 0000000000..6ddc74ae0f
--- /dev/null
+++ b/lib/chef/win32/api/command_line_helper.rb
@@ -0,0 +1,89 @@
+#
+# Author:: Kapil Chouhan <kapil.chouhan@msystechnologies.com>
+# Copyright:: Copyright 2013-2020, 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_relative "../api"
+
+class Chef
+ module ReservedNames::Win32
+ module API
+ module CommandLineHelper
+ # extend Chef::ReservedNames::Win32
+ extend Chef::ReservedNames::Win32::API
+
+ ###############################################
+ # Win32 API Bindings
+ ###############################################
+
+ ffi_lib "Shell32"
+
+=begin
+LPWSTR * CommandLineToArgvW(
+ LPCWSTR lpCmdLine,
+ int *pNumArgs
+);
+=end
+
+ safe_attach_function :command_line_to_argv_w, :CommandLineToArgvW, %i{pointer pointer}, :pointer
+
+ ffi_lib "Kernel32"
+
+=begin
+LPSTR GetCommandLineA();
+=end
+
+ safe_attach_function :get_command_line, :GetCommandLineA, [], :pointer
+
+=begin
+HLOCAL LocalFree(
+ _Frees_ptr_opt_ HLOCAL hMem
+);
+=end
+
+ safe_attach_function :local_free, :LocalFree, [:pointer], :pointer
+
+ ###############################################
+ # Helpers
+ ###############################################
+
+ # It takes the supplied string and splits it into an array.
+ def command_line_to_argv_w_helper(args)
+ arguments_list = []
+ argv = args.to_wstring
+ result = get_command_line
+ argc = FFI::MemoryPointer.new(:int)
+
+ # Parses a Unicode command line string
+ # It is return an array of pointers to the command line arguments.
+ # Along with a count of such arguments
+ result = command_line_to_argv_w(argv, argc)
+ str_ptr = result.read_pointer
+ offset = 0
+ number_of_agrs = argc.read_int
+ number_of_agrs.times do
+ new_str_pointer = str_ptr.+(offset)
+ argument = new_str_pointer.read_wstring
+ arguments_list << argument
+ offset = offset + argument.length * 2 + 2
+ end
+ local_free(result)
+ arguments_list
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/win32/api/crypto.rb b/lib/chef/win32/api/crypto.rb
index 0abb908622..219ec163be 100644
--- a/lib/chef/win32/api/crypto.rb
+++ b/lib/chef/win32/api/crypto.rb
@@ -1,6 +1,6 @@
#
# Author:: Jay Mundrawala (<jdm@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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/win32/api"
+require_relative "../api"
class Chef
module ReservedNames::Win32
@@ -35,8 +35,8 @@ class Chef
CRYPTPROTECT_AUDIT = 0x10
class CRYPT_INTEGER_BLOB < FFI::Struct
- layout :cbData, :DWORD, # Count, in bytes, of data
- :pbData, :pointer # Pointer to data buffer
+ layout :cbData, :DWORD, # Count, in bytes, of data
+ :pbData, :pointer # Pointer to data buffer
def initialize(str = nil)
super(nil)
if str
@@ -47,15 +47,15 @@ class Chef
end
- safe_attach_function :CryptProtectData, [
- :PDATA_BLOB,
- :LPCWSTR,
- :PDATA_BLOB,
- :pointer,
- :PCRYPTPROTECT_PROMPTSTRUCT,
- :DWORD,
- :PDATA_BLOB,
- ], :BOOL
+ safe_attach_function :CryptProtectData, %i{
+ PDATA_BLOB
+ LPCWSTR
+ PDATA_BLOB
+ pointer
+ PCRYPTPROTECT_PROMPTSTRUCT
+ DWORD
+ PDATA_BLOB
+ }, :BOOL
end
end
diff --git a/lib/chef/win32/api/error.rb b/lib/chef/win32/api/error.rb
index 12ccdb5ee9..0e1b943724 100644
--- a/lib/chef/win32/api/error.rb
+++ b/lib/chef/win32/api/error.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/win32/api"
+require_relative "../api"
class Chef
module ReservedNames::Win32
@@ -64,7 +64,7 @@ class Chef
ERROR_SHARING_VIOLATION = 32
ERROR_LOCK_VIOLATION = 33
ERROR_WRONG_DISK = 34
- ERROR_FCB_UNAVAILABLE = 35 # gets returned for some unsuccessful DeviceIoControl calls
+ ERROR_FCB_UNAVAILABLE = 35 # gets returned for some unsuccessful DeviceIoControl calls
ERROR_SHARING_BUFFER_EXCEEDED = 36
ERROR_HANDLE_EOF = 38
ERROR_HANDLE_DISK_FULL = 39
@@ -90,6 +90,7 @@ class Chef
ERROR_TOO_MANY_NAMES = 68
ERROR_TOO_MANY_SESS = 69
ERROR_SHARING_PAUSED = 70
+ # cspell:disable-next-line
ERROR_REQ_NOT_ACCEP = 71
ERROR_REDIR_PAUSED = 72
@@ -194,12 +195,12 @@ class Chef
ERROR_INVALID_EXE_SIGNATURE = 191
ERROR_EXE_MARKED_INVALID = 192
ERROR_BAD_EXE_FORMAT = 193
- ERROR_ITERATED_DATA_EXCEEDS_64k = 194 # rubocop:disable Style/ConstantName
+ ERROR_ITERATED_DATA_EXCEEDS_64k = 194 # rubocop:disable Naming/ConstantName
ERROR_INVALID_MINALLOCSIZE = 195
ERROR_DYNLINK_FROM_INVALID_RING = 196
ERROR_IOPL_NOT_ENABLED = 197
ERROR_INVALID_SEGDPL = 198
- ERROR_AUTODATASEG_EXCEEDS_64k = 199 # rubocop:disable Style/ConstantName
+ ERROR_AUTODATASEG_EXCEEDS_64k = 199 # rubocop:disable Naming/ConstantName
ERROR_RING2SEG_MUST_BE_MOVABLE = 200
ERROR_RELOC_CHAIN_XEEDS_SEGLIM = 201
ERROR_INFLOOP_IN_RELOC_CHAIN = 202
@@ -211,9 +212,9 @@ class Chef
ERROR_META_EXPANSION_TOO_LONG = 208 # if "*a" > 8.3
ERROR_INVALID_SIGNAL_NUMBER = 209
ERROR_THREAD_1_INACTIVE = 210
- ERROR_INFO_NOT_AVAIL = 211 #@@ PTM 5550
+ ERROR_INFO_NOT_AVAIL = 211 # @@ PTM 5550
ERROR_LOCKED = 212
- ERROR_BAD_DYNALINK = 213 #@@ PTM 5760
+ ERROR_BAD_DYNALINK = 213 # @@ PTM 5760
ERROR_TOO_MANY_MODULES = 214
ERROR_NESTING_NOT_ALLOWED = 215
ERROR_EXE_MACHINE_TYPE_MISMATCH = 216
@@ -876,6 +877,7 @@ class Chef
# Flags for LoadLibraryEx
+ # cspell:disable-next-line
DONT_RESOLVE_DLL_REFERENCES = 0x00000001
LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010
LOAD_LIBRARY_AS_DATAFILE = 0x00000002
@@ -905,8 +907,8 @@ DWORD WINAPI FormatMessage(
__in_opt va_list *Arguments
);
=end
- safe_attach_function :FormatMessageA, [:DWORD, :HANDLE, :DWORD, :DWORD, :LPTSTR, :DWORD, :varargs], :DWORD
- safe_attach_function :FormatMessageW, [:DWORD, :HANDLE, :DWORD, :DWORD, :LPWSTR, :DWORD, :varargs], :DWORD
+ safe_attach_function :FormatMessageA, %i{DWORD HANDLE DWORD DWORD LPTSTR DWORD varargs}, :DWORD
+ safe_attach_function :FormatMessageW, %i{DWORD HANDLE DWORD DWORD LPWSTR DWORD varargs}, :DWORD
=begin
DWORD WINAPI GetLastError(void);
@@ -918,7 +920,7 @@ void WINAPI SetLastError(
);
=end
safe_attach_function :SetLastError, [:DWORD], :void
- safe_attach_function :SetLastErrorEx, [:DWORD, :DWORD], :void
+ safe_attach_function :SetLastErrorEx, %i{DWORD DWORD}, :void
=begin
UINT WINAPI GetErrorMode(void);s
=end
@@ -938,7 +940,7 @@ HMODULE WINAPI LoadLibraryEx(
_In_ DWORD dwFlags
);
=end
- safe_attach_function :LoadLibraryExW, [:LPCTSTR, :HANDLE, :DWORD], :HANDLE
+ safe_attach_function :LoadLibraryExW, %i{LPCTSTR HANDLE DWORD}, :HANDLE
=begin
https://msdn.microsoft.com/en-us/library/windows/desktop/ms683152(v=vs.85).aspx
diff --git a/lib/chef/win32/api/file.rb b/lib/chef/win32/api/file.rb
index 7489c94fd9..c18bc08e8b 100644
--- a/lib/chef/win32/api/file.rb
+++ b/lib/chef/win32/api/file.rb
@@ -1,7 +1,7 @@
#
# Author:: Seth Chisamore (<schisamo@chef.io>)
# Author:: Mark Mzyk (<mmzyk@ospcode.com>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/win32/api"
-require "chef/win32/api/security"
-require "chef/win32/api/system"
-require "chef/win32/unicode"
+require_relative "../api"
+require_relative "security"
+require_relative "system"
+require_relative "../unicode"
class Chef
module ReservedNames::Win32
@@ -67,6 +67,7 @@ class Chef
MAX_PATH = 260
SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1
+ SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x2
FILE_NAME_NORMALIZED = 0x0
FILE_NAME_OPENED = 0x8
@@ -188,7 +189,7 @@ class Chef
# 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
+ :w_code_page, :WORD
end
=begin
@@ -199,7 +200,7 @@ typedef struct _FILETIME {
=end
class FILETIME < FFI::Struct
layout :dw_low_date_time, :DWORD,
- :dw_high_date_time, :DWORD
+ :dw_high_date_time, :DWORD
end
=begin
@@ -211,8 +212,8 @@ typedef struct _SECURITY_ATTRIBUTES {
=end
class SECURITY_ATTRIBUTES < FFI::Struct
layout :n_length, :DWORD,
- :lp_security_descriptor, :LPVOID,
- :b_inherit_handle, :DWORD
+ :lp_security_descriptor, :LPVOID,
+ :b_inherit_handle, :DWORD
end
=begin
@@ -231,15 +232,15 @@ typedef struct _WIN32_FIND_DATA {
=end
class WIN32_FIND_DATA < FFI::Struct
layout :dw_file_attributes, :DWORD,
- :ft_creation_time, FILETIME,
- :ft_last_access_time, FILETIME,
- :ft_last_write_time, FILETIME,
- :n_file_size_high, :DWORD,
- :n_file_size_low, :DWORD,
- :dw_reserved_0, :DWORD,
- :dw_reserved_1, :DWORD,
- :c_file_name, [:BYTE, MAX_PATH * 2],
- :c_alternate_file_name, [:BYTE, 14]
+ :ft_creation_time, FILETIME,
+ :ft_last_access_time, FILETIME,
+ :ft_last_write_time, FILETIME,
+ :n_file_size_high, :DWORD,
+ :n_file_size_low, :DWORD,
+ :dw_reserved_0, :DWORD,
+ :dw_reserved_1, :DWORD,
+ :c_file_name, [:BYTE, MAX_PATH * 2],
+ :c_alternate_file_name, [:BYTE, 14]
end
=begin
@@ -258,15 +259,15 @@ typedef struct _BY_HANDLE_FILE_INFORMATION {
=end
class BY_HANDLE_FILE_INFORMATION < FFI::Struct
layout :dw_file_attributes, :DWORD,
- :ft_creation_time, FILETIME,
- :ft_last_access_time, FILETIME,
- :ft_last_write_time, FILETIME,
- :dw_volume_serial_number, :DWORD,
- :n_file_size_high, :DWORD,
- :n_file_size_low, :DWORD,
- :n_number_of_links, :DWORD,
- :n_file_index_high, :DWORD,
- :n_file_index_low, :DWORD
+ :ft_creation_time, FILETIME,
+ :ft_last_access_time, FILETIME,
+ :ft_last_write_time, FILETIME,
+ :dw_volume_serial_number, :DWORD,
+ :n_file_size_high, :DWORD,
+ :n_file_size_low, :DWORD,
+ :n_number_of_links, :DWORD,
+ :n_file_index_high, :DWORD,
+ :n_file_index_low, :DWORD
end
=begin
@@ -315,6 +316,7 @@ typedef struct _REPARSE_DATA_BUFFER {
string_pointer.read_wstring(self[:PrintNameLength] / 2)
end
end
+
class REPARSE_DATA_BUFFER_MOUNT_POINT < FFI::Struct
layout :SubstituteNameOffset, :ushort,
:SubstituteNameLength, :ushort,
@@ -332,14 +334,17 @@ typedef struct _REPARSE_DATA_BUFFER {
string_pointer.read_wstring(self[:PrintNameLength] / 2)
end
end
+
class REPARSE_DATA_BUFFER_GENERIC < FFI::Struct
layout :DataBuffer, :uchar
end
+
class REPARSE_DATA_BUFFER_UNION < FFI::Union
layout :SymbolicLinkReparseBuffer, REPARSE_DATA_BUFFER_SYMBOLIC_LINK,
:MountPointReparseBuffer, REPARSE_DATA_BUFFER_MOUNT_POINT,
:GenericReparseBuffer, REPARSE_DATA_BUFFER_GENERIC
end
+
class REPARSE_DATA_BUFFER < FFI::Struct
layout :ReparseTag, :uint32,
:ReparseDataLength, :ushort,
@@ -368,7 +373,7 @@ HANDLE WINAPI CreateFile(
__in_opt HANDLE hTemplateFile
);
=end
- safe_attach_function :CreateFileW, [:LPCTSTR, :DWORD, :DWORD, :LPSECURITY_ATTRIBUTES, :DWORD, :DWORD, :pointer], :HANDLE
+ safe_attach_function :CreateFileW, %i{LPCTSTR DWORD DWORD LPSECURITY_ATTRIBUTES DWORD DWORD pointer}, :HANDLE
=begin
BOOL WINAPI FindClose(
@@ -392,7 +397,7 @@ DWORD WINAPI GetFinalPathNameByHandle(
__in DWORD dwFlags
);
=end
- safe_attach_function :GetFinalPathNameByHandleW, [:HANDLE, :LPTSTR, :DWORD, :DWORD], :DWORD
+ safe_attach_function :GetFinalPathNameByHandleW, %i{HANDLE LPTSTR DWORD DWORD}, :DWORD
=begin
BOOL WINAPI GetFileInformationByHandle(
@@ -400,7 +405,7 @@ BOOL WINAPI GetFileInformationByHandle(
__out LPBY_HANDLE_FILE_INFORMATION lpFileInformation
);
=end
- safe_attach_function :GetFileInformationByHandle, [:HANDLE, :LPBY_HANDLE_FILE_INFORMATION], :BOOL
+ safe_attach_function :GetFileInformationByHandle, %i{HANDLE LPBY_HANDLE_FILE_INFORMATION}, :BOOL
=begin
HANDLE WINAPI FindFirstFile(
@@ -408,7 +413,7 @@ HANDLE WINAPI FindFirstFile(
__out LPWIN32_FIND_DATA lpFindFileData
);
=end
- safe_attach_function :FindFirstFileW, [:LPCTSTR, :LPWIN32_FIND_DATA], :HANDLE
+ safe_attach_function :FindFirstFileW, %i{LPCTSTR LPWIN32_FIND_DATA}, :HANDLE
=begin
BOOL WINAPI CreateHardLink(
@@ -417,7 +422,7 @@ BOOL WINAPI CreateHardLink(
__reserved LPSECURITY_ATTRIBUTES lpSecurityAttributes
);
=end
- safe_attach_function :CreateHardLinkW, [:LPCTSTR, :LPCTSTR, :LPSECURITY_ATTRIBUTES], :BOOLEAN
+ safe_attach_function :CreateHardLinkW, %i{LPCTSTR LPCTSTR LPSECURITY_ATTRIBUTES}, :BOOLEAN
=begin
BOOLEAN WINAPI CreateSymbolicLink(
@@ -426,7 +431,7 @@ BOOLEAN WINAPI CreateSymbolicLink(
__in DWORD dwFlags
);
=end
- safe_attach_function :CreateSymbolicLinkW, [:LPTSTR, :LPTSTR, :DWORD], :BOOLEAN
+ safe_attach_function :CreateSymbolicLinkW, %i{LPTSTR LPTSTR DWORD}, :BOOLEAN
=begin
DWORD WINAPI GetLongPathName(
@@ -435,7 +440,7 @@ DWORD WINAPI GetLongPathName(
__in DWORD cchBuffer
);
=end
- safe_attach_function :GetLongPathNameW, [:LPCTSTR, :LPTSTR, :DWORD], :DWORD
+ safe_attach_function :GetLongPathNameW, %i{LPCTSTR LPTSTR DWORD}, :DWORD
=begin
DWORD WINAPI GetShortPathName(
@@ -444,7 +449,7 @@ DWORD WINAPI GetShortPathName(
__in DWORD cchBuffer
);
=end
- safe_attach_function :GetShortPathNameW, [:LPCTSTR, :LPTSTR, :DWORD], :DWORD
+ safe_attach_function :GetShortPathNameW, %i{LPCTSTR LPTSTR DWORD}, :DWORD
=begin
BOOL WINAPI DeviceIoControl(
@@ -458,25 +463,25 @@ BOOL WINAPI DeviceIoControl(
__inout_opt LPOVERLAPPED lpOverlapped
);
=end
- safe_attach_function :DeviceIoControl, [:HANDLE, :DWORD, :LPVOID, :DWORD, :LPVOID, :DWORD, :LPDWORD, :pointer], :BOOL
+ safe_attach_function :DeviceIoControl, %i{HANDLE DWORD LPVOID DWORD LPVOID DWORD LPDWORD pointer}, :BOOL
-#BOOL WINAPI DeleteVolumeMountPoint(
- #_In_ LPCTSTR lpszVolumeMountPoint
-#);
+ # 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 SetVolumeMountPoint(
+ # _In_ LPCTSTR lpszVolumeMountPoint,
+ # _In_ LPCTSTR lpszVolumeName
+ # );
+ safe_attach_function :SetVolumeMountPointW, %i{LPCTSTR LPCTSTR}, :BOOL
-#BOOL WINAPI GetVolumeNameForVolumeMountPoint(
- #_In_ LPCTSTR lpszVolumeMountPoint,
- #_Out_ LPTSTR lpszVolumeName,
- #_In_ DWORD cchBufferLength
-#);
- safe_attach_function :GetVolumeNameForVolumeMountPointW, [:LPCTSTR, :LPTSTR, :DWORD], :BOOL
+ # BOOL WINAPI GetVolumeNameForVolumeMountPoint(
+ # _In_ LPCTSTR lpszVolumeMountPoint,
+ # _Out_ LPTSTR lpszVolumeName,
+ # _In_ DWORD cchBufferLength
+ # );
+ safe_attach_function :GetVolumeNameForVolumeMountPointW, %i{LPCTSTR LPTSTR DWORD}, :BOOL
=begin
BOOL WINAPI GetFileVersionInfo(
@@ -486,7 +491,7 @@ BOOL WINAPI GetFileVersionInfo(
_Out_ LPVOID lpData
);
=end
- safe_attach_function :GetFileVersionInfoW, [:LPCTSTR, :DWORD, :DWORD, :LPVOID], :BOOL
+ safe_attach_function :GetFileVersionInfoW, %i{LPCTSTR DWORD DWORD LPVOID}, :BOOL
=begin
DWORD WINAPI GetFileVersionInfoSize(
@@ -494,7 +499,7 @@ DWORD WINAPI GetFileVersionInfoSize(
_Out_opt_ LPDWORD lpdwHandle
);
=end
- safe_attach_function :GetFileVersionInfoSizeW, [:LPCTSTR, :LPDWORD], :DWORD
+ safe_attach_function :GetFileVersionInfoSizeW, %i{LPCTSTR LPDWORD}, :DWORD
=begin
BOOL WINAPI VerQueryValue(
@@ -504,7 +509,7 @@ BOOL WINAPI VerQueryValue(
_Out_ PUINT puLen
);
=end
- safe_attach_function :VerQueryValueW, [:LPCVOID, :LPCTSTR, :LPVOID, :PUINT], :BOOL
+ safe_attach_function :VerQueryValueW, %i{LPCVOID LPCTSTR LPVOID PUINT}, :BOOL
###############################################
# Helpers
@@ -512,7 +517,7 @@ BOOL WINAPI VerQueryValue(
# takes the given path pre-pends "\\?\" and
# UTF-16LE encodes it. Used to prepare paths
- # to be passed to the *W vesion of WinAPI File
+ # to be passed to the *W version of WinAPI File
# functions.
# This function is used by the "Link" resources where we need
# preserve relative paths because symbolic links can actually
@@ -537,24 +542,22 @@ BOOL WINAPI VerQueryValue(
# ensures the handle is closed on exit of the block
# FIXME: yard with @yield
def file_search_handle(path)
- begin
- # Workaround for CHEF-4419:
- # Make sure paths starting with "/" has a drive letter
- # assigned from the current working diretory.
- # Note: With CHEF-4427 this issue will be fixed with a
- # broader fix to map all the paths starting with "/" to
- # SYSTEM_DRIVE on windows.
- path = ::File.expand_path(path) if path.start_with? "/"
- path = canonical_encode_path(path)
- find_data = WIN32_FIND_DATA.new
- handle = FindFirstFileW(path, find_data)
- if handle == INVALID_HANDLE_VALUE
- Chef::ReservedNames::Win32::Error.raise!
- end
- yield(handle, find_data)
- ensure
- FindClose(handle) if handle && handle != INVALID_HANDLE_VALUE
+ # Workaround for CHEF-4419:
+ # Make sure paths starting with "/" has a drive letter
+ # assigned from the current working directory.
+ # Note: With CHEF-4427 this issue will be fixed with a
+ # broader fix to map all the paths starting with "/" to
+ # SYSTEM_DRIVE on windows.
+ path = ::File.expand_path(path) if path.start_with? "/"
+ path = canonical_encode_path(path)
+ find_data = WIN32_FIND_DATA.new
+ handle = FindFirstFileW(path, find_data)
+ if handle == INVALID_HANDLE_VALUE
+ Chef::ReservedNames::Win32::Error.raise!
end
+ yield(handle, find_data)
+ ensure
+ FindClose(handle) if handle && handle != INVALID_HANDLE_VALUE
end
# retrieves a file handle and passes it
@@ -562,34 +565,30 @@ BOOL WINAPI VerQueryValue(
# ensures the handle is closed on exit of the block
# FIXME: yard with @yield
def file_handle(path)
- begin
- path = canonical_encode_path(path)
- handle = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ,
- nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, nil)
+ path = canonical_encode_path(path)
+ handle = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ,
+ nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, nil)
- if handle == INVALID_HANDLE_VALUE
- Chef::ReservedNames::Win32::Error.raise!
- end
- yield(handle)
- ensure
- CloseHandle(handle) if handle && handle != INVALID_HANDLE_VALUE
+ if handle == INVALID_HANDLE_VALUE
+ Chef::ReservedNames::Win32::Error.raise!
end
+ yield(handle)
+ ensure
+ CloseHandle(handle) if handle && handle != INVALID_HANDLE_VALUE
end
# FIXME: yard with @yield
def symlink_file_handle(path)
- begin
- path = encode_path(path)
- handle = CreateFileW(path, FILE_READ_EA, FILE_SHARE_READ,
- nil, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nil)
+ path = encode_path(path)
+ handle = CreateFileW(path, FILE_READ_EA, FILE_SHARE_READ,
+ nil, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nil)
- if handle == INVALID_HANDLE_VALUE
- Chef::ReservedNames::Win32::Error.raise!
- end
- yield(handle)
- ensure
- CloseHandle(handle) if handle && handle != INVALID_HANDLE_VALUE
+ if handle == INVALID_HANDLE_VALUE
+ Chef::ReservedNames::Win32::Error.raise!
end
+ yield(handle)
+ ensure
+ CloseHandle(handle) if handle && handle != INVALID_HANDLE_VALUE
end
def retrieve_file_info(file_name)
diff --git a/lib/chef/win32/api/installer.rb b/lib/chef/win32/api/installer.rb
index caf7b23f59..c9539d5c2d 100644
--- a/lib/chef/win32/api/installer.rb
+++ b/lib/chef/win32/api/installer.rb
@@ -1,6 +1,6 @@
#
# Author:: Bryan McLellan <btm@loftninjas.org>
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,10 +16,10 @@
# limitations under the License.
#
-require "chef/exceptions"
-require "chef/win32/api"
-require "chef/win32/error"
-require "pathname"
+require_relative "../../exceptions"
+require_relative "../api"
+require_relative "../error"
+require "pathname" unless defined?(Pathname)
class Chef
module ReservedNames::Win32
@@ -44,7 +44,7 @@ UINT MsiOpenPackage(
_Out_ MSIHANDLE *hProduct
);
=end
- safe_attach_function :msi_open_package, :MsiOpenPackageExA, [ :string, :int, :pointer ], :int
+ safe_attach_function :msi_open_package, :MsiOpenPackageExA, %i{string int pointer}, :int
=begin
UINT MsiGetProductProperty(
@@ -54,7 +54,7 @@ UINT MsiGetProductProperty(
_Inout_ DWORD *pcchValueBuf
);
=end
- safe_attach_function :msi_get_product_property, :MsiGetProductPropertyA, [ :pointer, :pointer, :pointer, :pointer ], :int
+ safe_attach_function :msi_get_product_property, :MsiGetProductPropertyA, %i{pointer pointer pointer pointer}, :int
=begin
UINT MsiGetProductInfo(
@@ -64,7 +64,7 @@ UINT MsiGetProductInfo(
_Inout_ DWORD *pcchValueBuf
);
=end
- safe_attach_function :msi_get_product_info, :MsiGetProductInfoA, [ :pointer, :pointer, :pointer, :pointer ], :int
+ safe_attach_function :msi_get_product_info, :MsiGetProductInfoA, %i{pointer pointer pointer pointer}, :int
=begin
UINT MsiCloseHandle(
@@ -107,7 +107,7 @@ UINT MsiCloseHandle(
end
msi_close_handle(pkg_ptr.read_pointer)
- return buffer.chomp(0.chr)
+ buffer.chomp(0.chr)
end
# Opens a Microsoft Installer (MSI) file from an absolute path and returns a pointer to a handle
@@ -124,7 +124,7 @@ UINT MsiCloseHandle(
else
raise Chef::Exceptions::Package, "msi_open_package: unexpected status #{status}: #{Chef::ReservedNames::Win32::Error.format_message(status)}"
end
- return pkg_ptr
+ pkg_ptr
end
# All installed product_codes should have a VersionString
diff --git a/lib/chef/win32/api/memory.rb b/lib/chef/win32/api/memory.rb
index a00ac5fec8..aed27663fc 100644
--- a/lib/chef/win32/api/memory.rb
+++ b/lib/chef/win32/api/memory.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/win32/api"
+require_relative "../api"
class Chef
module ReservedNames::Win32
@@ -56,7 +56,7 @@ HLOCAL WINAPI LocalAlloc(
__in SIZE_T uBytes
);
=end
- safe_attach_function :LocalAlloc, [ :UINT, :SIZE_T ], :pointer
+ safe_attach_function :LocalAlloc, %i{UINT SIZE_T}, :pointer
=begin
UINT WINAPI LocalFlags(
@@ -79,7 +79,7 @@ HLOCAL WINAPI LocalReAlloc(
__in UINT uFlags
);
=end
- safe_attach_function :LocalReAlloc, [ :pointer, :SIZE_T, :UINT ], :pointer
+ safe_attach_function :LocalReAlloc, %i{pointer SIZE_T UINT}, :pointer
=begin
UINT WINAPI LocalSize(
@@ -95,9 +95,9 @@ UINT WINAPI LocalSize(
ffi_lib FFI::Library::LIBC
safe_attach_function :malloc, [:size_t], :pointer
safe_attach_function :calloc, [:size_t], :pointer
- safe_attach_function :realloc, [:pointer, :size_t], :pointer
+ safe_attach_function :realloc, %i{pointer size_t}, :pointer
safe_attach_function :free, [:pointer], :void
- safe_attach_function :memcpy, [:pointer, :pointer, :size_t], :pointer
+ safe_attach_function :memcpy, %i{pointer pointer size_t}, :pointer
end
end
diff --git a/lib/chef/win32/api/net.rb b/lib/chef/win32/api/net.rb
index abf0dd83ec..374c2e231e 100644
--- a/lib/chef/win32/api/net.rb
+++ b/lib/chef/win32/api/net.rb
@@ -1,6 +1,6 @@
#
# Author:: Serdar Sutay (<serdar@chef.io>)
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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/win32/api"
-require "chef/win32/unicode"
+require_relative "../api"
+require_relative "../unicode"
class Chef
module ReservedNames::Win32
@@ -39,13 +39,14 @@ class Chef
UF_ACCOUNTDISABLE = 0x000002
UF_PASSWD_CANT_CHANGE = 0x000040
UF_NORMAL_ACCOUNT = 0x000200
+ # cspell:disable-next-line
UF_DONT_EXPIRE_PASSWD = 0x010000
USE_NOFORCE = 0
USE_FORCE = 1
- USE_LOTS_OF_FORCE = 2 #every windows API should support this flag
+ USE_LOTS_OF_FORCE = 2 # every windows API should support this flag
- NERR_Success = 0 # rubocop:disable Style/ConstantName
+ NERR_Success = 0 # rubocop:disable Naming/ConstantName
ERROR_MORE_DATA = 234
ffi_lib "netapi32"
@@ -144,6 +145,11 @@ class Chef
layout :lgrpi0_name, :LPWSTR
end
+ class LOCALGROUP_INFO_1 < FFI::Struct
+ layout :lgrpi1_name, :LPWSTR,
+ :lgrpi1_comment, :LPWSTR
+ end
+
class USE_INFO_2 < FFI::Struct
include StructHelpers
@@ -158,36 +164,47 @@ class Chef
:ui2_domainname, :LMSTR
end
- #NET_API_STATUS NetLocalGroupAdd(
- #_In_ LPCWSTR servername,
- #_In_ DWORD level,
- #_In_ LPBYTE buf,
- #_Out_ LPDWORD parm_err
- #);
- safe_attach_function :NetLocalGroupAdd, [
- :LPCWSTR, :DWORD, :LPBYTE, :LPDWORD
- ], :DWORD
-
- #NET_API_STATUS NetLocalGroupDel(
- #_In_ LPCWSTR servername,
- #_In_ LPCWSTR groupname
- #);
- safe_attach_function :NetLocalGroupDel, [:LPCWSTR, :LPCWSTR], :DWORD
-
- #NET_API_STATUS NetLocalGroupGetMembers(
- #_In_ LPCWSTR servername,
- #_In_ LPCWSTR localgroupname,
- #_In_ DWORD level,
- #_Out_ LPBYTE *bufptr,
- #_In_ DWORD prefmaxlen,
- #_Out_ LPDWORD entriesread,
- #_Out_ LPDWORD totalentries,
- #_Inout_ PDWORD_PTR resumehandle
- #);
- safe_attach_function :NetLocalGroupGetMembers, [
- :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD,
- :LPDWORD, :LPDWORD, :PDWORD_PTR
- ], :DWORD
+ # NET_API_STATUS NetLocalGroupAdd(
+ # _In_ LPCWSTR servername,
+ # _In_ DWORD level,
+ # _In_ LPBYTE buf,
+ # _Out_ LPDWORD parm_err
+ # );
+ safe_attach_function :NetLocalGroupAdd, %i{
+ LPCWSTR DWORD LPBYTE LPDWORD
+ }, :DWORD
+
+ # NET_API_STATUS NetLocalGroupSetInfo(
+ # _In_ LPCWSTR servername,
+ # _In_ LPCWSTR groupname,
+ # _In_ DWORD level,
+ # _In_ LPBYTE buf,
+ # _Out_ LPDWORD parm_err
+ # );
+ safe_attach_function :NetLocalGroupSetInfo, %i{
+ LPCWSTR LPCWSTR DWORD LPBYTE LPDWORD
+ }, :DWORD
+
+ # NET_API_STATUS NetLocalGroupDel(
+ # _In_ LPCWSTR servername,
+ # _In_ LPCWSTR groupname
+ # );
+ safe_attach_function :NetLocalGroupDel, %i{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, %i{
+ LPCWSTR LPCWSTR DWORD LPBYTE DWORD
+ LPDWORD LPDWORD PDWORD_PTR
+ }, :DWORD
# NET_API_STATUS NetUserEnum(
# _In_ LPCWSTR servername,
@@ -199,113 +216,113 @@ class Chef
# _Out_ LPDWORD totalentries,
# _Inout_ LPDWORD resume_handle
# );
- safe_attach_function :NetUserEnum, [
- :LPCWSTR, :DWORD, :DWORD, :LPBYTE,
- :DWORD, :LPDWORD, :LPDWORD, :LPDWORD
- ], :DWORD
+ safe_attach_function :NetUserEnum, %i{
+ 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(
+ # NET_API_STATUS NetUserAdd(
+ # _In_ LMSTR servername,
+ # _In_ DWORD level,
+ # _In_ LPBYTE buf,
+ # _Out_ LPDWORD parm_err
+ # );
+ safe_attach_function :NetUserAdd, %i{
+ 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
+ # );
+ safe_attach_function :NetLocalGroupAddMembers, %i{
+ LPCWSTR LPCWSTR DWORD LPBYTE DWORD
+ }, :DWORD
- #NET_API_STATUS NetLocalGroupSetMembers(
+ # 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
+ # );
+ safe_attach_function :NetLocalGroupSetMembers, %i{
+ LPCWSTR LPCWSTR DWORD LPBYTE DWORD
+ }, :DWORD
- #NET_API_STATUS NetLocalGroupDelMembers(
+ # 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
+ # );
+ safe_attach_function :NetLocalGroupDelMembers, %i{
+ LPCWSTR LPCWSTR DWORD LPBYTE DWORD
+ }, :DWORD
- #NET_API_STATUS NetUserGetInfo(
+ # 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
+ # );
+ safe_attach_function :NetUserGetInfo, %i{
+ LPCWSTR LPCWSTR DWORD LPBYTE
+ }, :DWORD
- #NET_API_STATUS NetApiBufferFree(
+ # NET_API_STATUS NetApiBufferFree(
# _In_ LPVOID Buffer
- #);
+ # );
safe_attach_function :NetApiBufferFree, [:LPVOID], :DWORD
- #NET_API_STATUS NetUserSetInfo(
+ # 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
+ # );
+ safe_attach_function :NetUserSetInfo, %i{
+ LPCWSTR LPCWSTR DWORD LPBYTE LPDWORD
+ }, :DWORD
- #NET_API_STATUS NetUserDel(
+ # 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
+ # );
+ safe_attach_function :NetUserDel, %i{LPCWSTR LPCWSTR}, :DWORD
+
+ # NET_API_STATUS NetUseDel(
+ # _In_ LMSTR UncServerName,
+ # _In_ LMSTR UseName,
+ # _In_ DWORD ForceCond
+ # );
+ safe_attach_function :NetUseDel, %i{LMSTR LMSTR DWORD}, :DWORD
+
+ # NET_API_STATUS NetUseGetInfo(
+ # _In_ LMSTR UncServerName,
+ # _In_ LMSTR UseName,
+ # _In_ DWORD Level,
+ # _Out_ LPBYTE *BufPtr
+ # );
+ safe_attach_function :NetUseGetInfo, %i{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, %i{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 3568b7e76c..be2c765910 100644
--- a/lib/chef/win32/api/process.rb
+++ b/lib/chef/win32/api/process.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/win32/api"
+require_relative "../api"
class Chef
module ReservedNames::Win32
@@ -31,10 +31,10 @@ class Chef
ffi_lib "kernel32"
safe_attach_function :GetCurrentProcess, [], :HANDLE
- safe_attach_function :GetProcessHandleCount, [ :HANDLE, :LPDWORD ], :BOOL
+ safe_attach_function :GetProcessHandleCount, %i{HANDLE LPDWORD}, :BOOL
safe_attach_function :GetProcessId, [ :HANDLE ], :DWORD
safe_attach_function :CloseHandle, [ :HANDLE ], :BOOL
- safe_attach_function :IsWow64Process, [ :HANDLE, :PBOOL ], :BOOL
+ safe_attach_function :IsWow64Process, %i{HANDLE PBOOL}, :BOOL
end
end
diff --git a/lib/chef/win32/api/psapi.rb b/lib/chef/win32/api/psapi.rb
index 9deb68d92e..dff90258d4 100644
--- a/lib/chef/win32/api/psapi.rb
+++ b/lib/chef/win32/api/psapi.rb
@@ -1,6 +1,6 @@
#
# Author:: Seth Chisamore (<schisamo@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/win32/api"
+require_relative "../api"
class Chef
module ReservedNames::Win32
@@ -43,7 +43,7 @@ class Chef
ffi_lib "psapi"
- safe_attach_function :GetProcessMemoryInfo, [ :HANDLE, :pointer, :DWORD ], :BOOL
+ safe_attach_function :GetProcessMemoryInfo, %i{HANDLE pointer DWORD}, :BOOL
end
end
diff --git a/lib/chef/win32/api/registry.rb b/lib/chef/win32/api/registry.rb
index dec25118a3..ac339a3f96 100644
--- a/lib/chef/win32/api/registry.rb
+++ b/lib/chef/win32/api/registry.rb
@@ -1,6 +1,6 @@
#
# Author:: Salim Alam (<salam@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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/win32/api"
+require_relative "../api"
class Chef
module ReservedNames::Win32
@@ -36,14 +36,14 @@ class Chef
# _In_ REGSAM samDesired,
# _Reserved_ DWORD Reserved
# );
- safe_attach_function :RegDeleteKeyExW, [ :HKEY, :LPCTSTR, :LONG, :DWORD ], :LONG
- safe_attach_function :RegDeleteKeyExA, [ :HKEY, :LPCTSTR, :LONG, :DWORD ], :LONG
+ safe_attach_function :RegDeleteKeyExW, %i{HKEY LPCTSTR LONG DWORD}, :LONG
+ safe_attach_function :RegDeleteKeyExA, %i{HKEY LPCTSTR LONG DWORD}, :LONG
# LONG WINAPI RegDeleteValue(
# _In_ HKEY hKey,
# _In_opt_ LPCTSTR lpValueName
# );
- safe_attach_function :RegDeleteValueW, [ :HKEY, :LPCTSTR ], :LONG
+ safe_attach_function :RegDeleteValueW, %i{HKEY LPCTSTR}, :LONG
end
end
diff --git a/lib/chef/win32/api/security.rb b/lib/chef/win32/api/security.rb
index 64df077686..e50a870613 100644
--- a/lib/chef/win32/api/security.rb
+++ b/lib/chef/win32/api/security.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/win32/api"
+require_relative "../api"
class Chef
module ReservedNames::Win32
@@ -140,6 +140,8 @@ class Chef
FILE_READ_EA | SYNCHRONIZE
FILE_GENERIC_WRITE = STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | SYNCHRONIZE
FILE_GENERIC_EXECUTE = STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES | FILE_EXECUTE | SYNCHRONIZE
+ WRITE = FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA
+ SUBFOLDERS_AND_FILES_ONLY = INHERIT_ONLY_ACE | CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE
# Access Token Rights (for OpenProcessToken)
# Access Rights for Access-Token Objects (used in OpenProcessToken)
TOKEN_ASSIGN_PRIMARY = 0x0001
@@ -182,18 +184,18 @@ 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;
+ 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
@@ -214,21 +216,21 @@ class Chef
# Win32 API Bindings
###############################################
- SE_OBJECT_TYPE = enum :SE_OBJECT_TYPE, [
- :SE_UNKNOWN_OBJECT_TYPE,
- :SE_FILE_OBJECT,
- :SE_SERVICE,
- :SE_PRINTER,
- :SE_REGISTRY_KEY,
- :SE_LMSHARE,
- :SE_KERNEL_OBJECT,
- :SE_WINDOW_OBJECT,
- :SE_DS_OBJECT,
- :SE_DS_OBJECT_ALL,
- :SE_PROVIDER_DEFINED_OBJECT,
- :SE_WMIGUID_OBJECT,
- :SE_REGISTRY_WOW64_32KEY,
- ]
+ SE_OBJECT_TYPE = enum :SE_OBJECT_TYPE, %i{
+ SE_UNKNOWN_OBJECT_TYPE
+ SE_FILE_OBJECT
+ SE_SERVICE
+ SE_PRINTER
+ SE_REGISTRY_KEY
+ SE_LMSHARE
+ SE_KERNEL_OBJECT
+ SE_WINDOW_OBJECT
+ SE_DS_OBJECT
+ SE_DS_OBJECT_ALL
+ SE_PROVIDER_DEFINED_OBJECT
+ SE_WMIGUID_OBJECT
+ SE_REGISTRY_WOW64_32KEY
+ }
SID_NAME_USE = enum :SID_NAME_USE, [
:SidTypeUser, 1,
@@ -296,13 +298,24 @@ class Chef
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,
+ SECURITY_IMPERSONATION_LEVEL = enum :SECURITY_IMPERSONATION_LEVEL, %i{
+ SecurityAnonymous
+ SecurityIdentification
+ SecurityImpersonation
+ SecurityDelegation
+ }
+
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/bb530718%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
+ ELEVATION_TYPE = enum :ELEVATION_TYPE, [
+ :TokenElevationTypeDefault, 1,
+ :TokenElevationTypeFull,
+ :TokenElevationTypeLimited
]
+ class TOKEN_ELEVATION_TYPE < FFI::Struct
+ layout :ElevationType, :ELEVATION_TYPE
+ end
+
# SECURITY_DESCRIPTOR is an opaque structure whose contents can vary. Pass the
# pointer around and free it with LocalFree.
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa379561(v=vs.85).aspx
@@ -313,24 +326,24 @@ class Chef
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa374931(v=VS.85).aspx
class ACLStruct < FFI::Struct
layout :AclRevision, :uchar,
- :Sbzl, :uchar,
- :AclSize, :ushort,
- :AceCount, :ushort,
- :Sbz2, :ushort
+ :Sbzl, :uchar,
+ :AclSize, :ushort,
+ :AceCount, :ushort,
+ :Sbz2, :ushort
end
class ACE_HEADER < FFI::Struct
layout :AceType, :uchar,
- :AceFlags, :uchar,
- :AceSize, :ushort
+ :AceFlags, :uchar,
+ :AceSize, :ushort
end
class ACE_WITH_MASK_AND_SID < FFI::Struct
layout :AceType, :uchar,
- :AceFlags, :uchar,
- :AceSize, :ushort,
- :Mask, :uint32,
- :SidStart, :uint32
+ :AceFlags, :uchar,
+ :AceSize, :ushort,
+ :Mask, :uint32,
+ :SidStart, :uint32
# The AceTypes this structure supports
def self.supports?(ace_type)
@@ -345,12 +358,12 @@ class Chef
class LUID < FFI::Struct
layout :LowPart, :DWORD,
- :HighPart, :LONG
+ :HighPart, :LONG
end
class LUID_AND_ATTRIBUTES < FFI::Struct
layout :Luid, LUID,
- :Attributes, :DWORD
+ :Attributes, :DWORD
end
class GENERIC_MAPPING < FFI::Struct
@@ -362,13 +375,13 @@ class Chef
class PRIVILEGE_SET < FFI::Struct
layout :PrivilegeCount, :DWORD,
- :Control, :DWORD,
- :Privilege, [LUID_AND_ATTRIBUTES, 1]
+ :Control, :DWORD,
+ :Privilege, [LUID_AND_ATTRIBUTES, 1]
end
class TOKEN_PRIVILEGES < FFI::Struct
layout :PrivilegeCount, :DWORD,
- :Privileges, LUID_AND_ATTRIBUTES
+ :Privileges, LUID_AND_ATTRIBUTES
def self.size_with_privileges(num_privileges)
offset_of(:Privileges) + LUID_AND_ATTRIBUTES.size * num_privileges
@@ -386,73 +399,82 @@ class Chef
# 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
+ :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
+ :MaximumLength, :USHORT,
+ :Buffer, :PWSTR
+ end
+
+ # https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/ns-ntsecapi-lsa_enumeration_information
+ class LSA_ENUMERATION_INFORMATION < FFI::Struct
+ layout :Sid, :PSID
end
ffi_lib "advapi32"
- safe_attach_function :AccessCheck, [:pointer, :HANDLE, :DWORD, :pointer, :pointer, :pointer, :pointer, :pointer], :BOOL
- safe_attach_function :AddAce, [ :pointer, :DWORD, :DWORD, :LPVOID, :DWORD ], :BOOL
- safe_attach_function :AddAccessAllowedAce, [ :pointer, :DWORD, :DWORD, :pointer ], :BOOL
- safe_attach_function :AddAccessAllowedAceEx, [ :pointer, :DWORD, :DWORD, :DWORD, :pointer ], :BOOL
- safe_attach_function :AddAccessDeniedAce, [ :pointer, :DWORD, :DWORD, :pointer ], :BOOL
- safe_attach_function :AddAccessDeniedAceEx, [ :pointer, :DWORD, :DWORD, :DWORD, :pointer ], :BOOL
- safe_attach_function :AdjustTokenPrivileges, [ :HANDLE, :BOOL, :pointer, :DWORD, :pointer, :PDWORD ], :BOOL
- safe_attach_function :ConvertSidToStringSidA, [ :pointer, :pointer ], :BOOL
- safe_attach_function :ConvertStringSidToSidW, [ :pointer, :pointer ], :BOOL
- safe_attach_function :DeleteAce, [ :pointer, :DWORD ], :BOOL
- safe_attach_function :DuplicateToken, [:HANDLE, :SECURITY_IMPERSONATION_LEVEL, :PHANDLE], :BOOL
- safe_attach_function :EqualSid, [ :pointer, :pointer ], :BOOL
+ safe_attach_function :AccessCheck, %i{pointer HANDLE DWORD pointer pointer pointer pointer pointer}, :BOOL
+ safe_attach_function :AddAce, %i{pointer DWORD DWORD LPVOID DWORD}, :BOOL
+ safe_attach_function :AddAccessAllowedAce, %i{pointer DWORD DWORD pointer}, :BOOL
+ safe_attach_function :AddAccessAllowedAceEx, %i{pointer DWORD DWORD DWORD pointer}, :BOOL
+ safe_attach_function :AddAccessDeniedAce, %i{pointer DWORD DWORD pointer}, :BOOL
+ safe_attach_function :AddAccessDeniedAceEx, %i{pointer DWORD DWORD DWORD pointer}, :BOOL
+ safe_attach_function :AdjustTokenPrivileges, %i{HANDLE BOOL pointer DWORD pointer PDWORD}, :BOOL
+ safe_attach_function :ConvertSidToStringSidA, %i{pointer pointer}, :BOOL
+ safe_attach_function :ConvertStringSidToSidW, %i{pointer pointer}, :BOOL
+ safe_attach_function :DeleteAce, %i{pointer DWORD}, :BOOL
+ safe_attach_function :DuplicateToken, %i{HANDLE SECURITY_IMPERSONATION_LEVEL PHANDLE}, :BOOL
+ safe_attach_function :EqualSid, %i{pointer pointer}, :BOOL
safe_attach_function :FreeSid, [ :pointer ], :pointer
- safe_attach_function :GetAce, [ :pointer, :DWORD, :pointer ], :BOOL
- safe_attach_function :GetFileSecurityW, [:LPCWSTR, :DWORD, :pointer, :DWORD, :pointer], :BOOL
+ safe_attach_function :GetAce, %i{pointer DWORD pointer}, :BOOL
+ safe_attach_function :GetFileSecurityW, %i{LPCWSTR DWORD pointer DWORD pointer}, :BOOL
safe_attach_function :GetLengthSid, [ :pointer ], :DWORD
- safe_attach_function :GetNamedSecurityInfoW, [ :LPWSTR, :SE_OBJECT_TYPE, :DWORD, :pointer, :pointer, :pointer, :pointer, :pointer ], :DWORD
- safe_attach_function :GetSecurityDescriptorControl, [ :pointer, :PWORD, :LPDWORD], :BOOL
- safe_attach_function :GetSecurityDescriptorDacl, [ :pointer, :LPBOOL, :pointer, :LPBOOL ], :BOOL
- safe_attach_function :GetSecurityDescriptorGroup, [ :pointer, :pointer, :LPBOOL], :BOOL
- safe_attach_function :GetSecurityDescriptorOwner, [ :pointer, :pointer, :LPBOOL], :BOOL
- safe_attach_function :GetSecurityDescriptorSacl, [ :pointer, :LPBOOL, :pointer, :LPBOOL ], :BOOL
- safe_attach_function :InitializeAcl, [ :pointer, :DWORD, :DWORD ], :BOOL
- safe_attach_function :InitializeSecurityDescriptor, [ :pointer, :DWORD ], :BOOL
+ safe_attach_function :GetNamedSecurityInfoW, %i{LPWSTR SE_OBJECT_TYPE DWORD pointer pointer pointer pointer pointer}, :DWORD
+ safe_attach_function :GetSecurityDescriptorControl, %i{pointer PWORD LPDWORD}, :BOOL
+ safe_attach_function :GetSecurityDescriptorDacl, %i{pointer LPBOOL pointer LPBOOL}, :BOOL
+ safe_attach_function :GetSecurityDescriptorGroup, %i{pointer pointer LPBOOL}, :BOOL
+ safe_attach_function :GetSecurityDescriptorOwner, %i{pointer pointer LPBOOL}, :BOOL
+ safe_attach_function :GetSecurityDescriptorSacl, %i{pointer LPBOOL pointer LPBOOL}, :BOOL
+ safe_attach_function :InitializeAcl, %i{pointer DWORD DWORD}, :BOOL
+ safe_attach_function :InitializeSecurityDescriptor, %i{pointer DWORD}, :BOOL
safe_attach_function :IsValidAcl, [ :pointer ], :BOOL
safe_attach_function :IsValidSecurityDescriptor, [ :pointer ], :BOOL
safe_attach_function :IsValidSid, [ :pointer ], :BOOL
- safe_attach_function :LookupAccountNameW, [ :LPCWSTR, :LPCWSTR, :pointer, :LPDWORD, :LPWSTR, :LPDWORD, :pointer ], :BOOL
- safe_attach_function :LookupAccountSidW, [ :LPCWSTR, :pointer, :LPWSTR, :LPDWORD, :LPWSTR, :LPDWORD, :pointer ], :BOOL
- 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 :LookupAccountNameW, %i{LPCWSTR LPCWSTR pointer LPDWORD LPWSTR LPDWORD pointer}, :BOOL
+ safe_attach_function :LookupAccountSidW, %i{LPCWSTR pointer LPWSTR LPDWORD LPWSTR LPDWORD pointer}, :BOOL
+ safe_attach_function :LookupPrivilegeNameW, %i{LPCWSTR PLUID LPWSTR LPDWORD}, :BOOL
+ safe_attach_function :LookupPrivilegeDisplayNameW, %i{LPCWSTR LPCWSTR LPWSTR LPDWORD LPDWORD}, :BOOL
+ safe_attach_function :LookupPrivilegeValueW, %i{LPCWSTR LPCWSTR PLUID}, :BOOL
+ safe_attach_function :LsaAddAccountRights, %i{pointer pointer pointer ULONG}, :NTSTATUS
+ safe_attach_function :LsaEnumerateAccountsWithUserRight, %i{LSA_HANDLE PLSA_UNICODE_STRING PVOID PULONG}, :NTSTATUS
+ safe_attach_function :LsaRemoveAccountRights, %i{pointer pointer BOOL 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 :LsaEnumerateAccountRights, %i{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
- safe_attach_function :QuerySecurityAccessMask, [ :DWORD, :LPDWORD ], :void
- safe_attach_function :SetFileSecurityW, [ :LPWSTR, :DWORD, :pointer ], :BOOL
- safe_attach_function :SetNamedSecurityInfoW, [ :LPWSTR, :SE_OBJECT_TYPE, :DWORD, :pointer, :pointer, :pointer, :pointer ], :DWORD
- safe_attach_function :SetSecurityAccessMask, [ :DWORD, :LPDWORD ], :void
- safe_attach_function :SetSecurityDescriptorDacl, [ :pointer, :BOOL, :pointer, :BOOL ], :BOOL
- safe_attach_function :SetSecurityDescriptorGroup, [ :pointer, :pointer, :BOOL ], :BOOL
- safe_attach_function :SetSecurityDescriptorOwner, [ :pointer, :pointer, :BOOL ], :BOOL
- safe_attach_function :SetSecurityDescriptorSacl, [ :pointer, :BOOL, :pointer, :BOOL ], :BOOL
- safe_attach_function :GetTokenInformation, [ :HANDLE, :TOKEN_INFORMATION_CLASS, :pointer, :DWORD, :PDWORD ], :BOOL
- safe_attach_function :LogonUserW, [:LPTSTR, :LPTSTR, :LPTSTR, :DWORD, :DWORD, :PHANDLE], :BOOL
+ safe_attach_function :LsaOpenPolicy, %i{PLSA_UNICODE_STRING PLSA_OBJECT_ATTRIBUTES DWORD PLSA_HANDLE}, :NTSTATUS
+ safe_attach_function :MakeAbsoluteSD, %i{pointer pointer LPDWORD pointer LPDWORD pointer LPDWORD pointer LPDWORD pointer LPDWORD}, :BOOL
+ safe_attach_function :MapGenericMask, %i{PDWORD PGENERICMAPPING}, :void
+ safe_attach_function :OpenProcessToken, %i{HANDLE DWORD PHANDLE}, :BOOL
+ safe_attach_function :QuerySecurityAccessMask, %i{DWORD LPDWORD}, :void
+ safe_attach_function :SetFileSecurityW, %i{LPWSTR DWORD pointer}, :BOOL
+ safe_attach_function :SetNamedSecurityInfoW, %i{LPWSTR SE_OBJECT_TYPE DWORD pointer pointer pointer pointer}, :DWORD
+ safe_attach_function :SetSecurityAccessMask, %i{DWORD LPDWORD}, :void
+ safe_attach_function :SetSecurityDescriptorDacl, %i{pointer BOOL pointer BOOL}, :BOOL
+ safe_attach_function :SetSecurityDescriptorGroup, %i{pointer pointer BOOL}, :BOOL
+ safe_attach_function :SetSecurityDescriptorOwner, %i{pointer pointer BOOL}, :BOOL
+ safe_attach_function :SetSecurityDescriptorSacl, %i{pointer BOOL pointer BOOL}, :BOOL
+ safe_attach_function :GetTokenInformation, %i{HANDLE TOKEN_INFORMATION_CLASS pointer DWORD PDWORD}, :BOOL
+ safe_attach_function :LogonUserW, %i{LPTSTR LPTSTR LPTSTR DWORD DWORD PHANDLE}, :BOOL
+ safe_attach_function :ImpersonateLoggedOnUser, [:HANDLE], :BOOL
+ safe_attach_function :RevertToSelf, [], :BOOL
end
end
diff --git a/lib/chef/win32/api/synchronization.rb b/lib/chef/win32/api/synchronization.rb
index 9b5d5c6ab9..cf9a101558 100644
--- a/lib/chef/win32/api/synchronization.rb
+++ b/lib/chef/win32/api/synchronization.rb
@@ -1,6 +1,6 @@
#
# Author:: Serdar Sutay (<serdar@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/win32/api"
+require_relative "../api"
class Chef
module ReservedNames::Win32
@@ -56,8 +56,8 @@ HANDLE WINAPI CreateMutex(
_In_opt_ LPCTSTR lpName
);
=end
- safe_attach_function :CreateMutexW, [ :LPSECURITY_ATTRIBUTES, :BOOL, :LPCTSTR ], :HANDLE
- safe_attach_function :CreateMutexA, [ :LPSECURITY_ATTRIBUTES, :BOOL, :LPCTSTR ], :HANDLE
+ safe_attach_function :CreateMutexW, %i{LPSECURITY_ATTRIBUTES BOOL LPCTSTR}, :HANDLE
+ safe_attach_function :CreateMutexA, %i{LPSECURITY_ATTRIBUTES BOOL LPCTSTR}, :HANDLE
=begin
DWORD WINAPI WaitForSingleObject(
@@ -65,7 +65,7 @@ DWORD WINAPI WaitForSingleObject(
_In_ DWORD dwMilliseconds
);
=end
- safe_attach_function :WaitForSingleObject, [ :HANDLE, :DWORD ], :DWORD
+ safe_attach_function :WaitForSingleObject, %i{HANDLE DWORD}, :DWORD
=begin
BOOL WINAPI ReleaseMutex(
@@ -81,8 +81,8 @@ HANDLE WINAPI OpenMutex(
_In_ LPCTSTR lpName
);
=end
- safe_attach_function :OpenMutexW, [ :DWORD, :BOOL, :LPCTSTR ], :HANDLE
- safe_attach_function :OpenMutexA, [ :DWORD, :BOOL, :LPCTSTR ], :HANDLE
+ safe_attach_function :OpenMutexW, %i{DWORD BOOL LPCTSTR}, :HANDLE
+ safe_attach_function :OpenMutexA, %i{DWORD BOOL LPCTSTR}, :HANDLE
end
end
end
diff --git a/lib/chef/win32/api/system.rb b/lib/chef/win32/api/system.rb
index 732ed073e6..924c9c1e29 100644
--- a/lib/chef/win32/api/system.rb
+++ b/lib/chef/win32/api/system.rb
@@ -1,6 +1,6 @@
#
# Author:: Seth Chisamore (<schisamo@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/win32/api"
+require_relative "../api"
class Chef
module ReservedNames::Win32
@@ -177,7 +177,7 @@ BOOL WINAPI GetProductInfo(
__out PDWORD pdwReturnedProductType
);
=end
- safe_attach_function :GetProductInfo, [:DWORD, :DWORD, :DWORD, :DWORD, :PDWORD], :BOOL
+ safe_attach_function :GetProductInfo, %i{DWORD DWORD DWORD DWORD PDWORD}, :BOOL
=begin
int WINAPI GetSystemMetrics(
@@ -192,8 +192,8 @@ UINT WINAPI GetSystemWow64Directory(
_In_ UINT uSize
);
=end
- safe_attach_function :GetSystemWow64DirectoryW, [:LPTSTR, :UINT], :UINT
- safe_attach_function :GetSystemWow64DirectoryA, [:LPTSTR, :UINT], :UINT
+ safe_attach_function :GetSystemWow64DirectoryW, %i{LPTSTR UINT}, :UINT
+ safe_attach_function :GetSystemWow64DirectoryA, %i{LPTSTR UINT}, :UINT
=begin
BOOL WINAPI Wow64DisableWow64FsRedirection(
@@ -220,8 +220,8 @@ LRESULT WINAPI SendMessageTimeout(
_Out_opt_ PDWORD_PTR lpdwResult
);
=end
- safe_attach_function :SendMessageTimeoutW, [:HWND, :UINT, :WPARAM, :LPARAM, :UINT, :UINT, :PDWORD_PTR], :LRESULT
- safe_attach_function :SendMessageTimeoutA, [:HWND, :UINT, :WPARAM, :LPARAM, :UINT, :UINT, :PDWORD_PTR], :LRESULT
+ safe_attach_function :SendMessageTimeoutW, %i{HWND UINT WPARAM LPARAM UINT UINT PDWORD_PTR}, :LRESULT
+ safe_attach_function :SendMessageTimeoutA, %i{HWND UINT WPARAM LPARAM UINT UINT PDWORD_PTR}, :LRESULT
=begin
DWORD WINAPI ExpandEnvironmentStrings(
@@ -230,8 +230,8 @@ DWORD WINAPI ExpandEnvironmentStrings(
_In_ DWORD nSize
);
=end
- safe_attach_function :ExpandEnvironmentStringsW, [:pointer, :pointer, :DWORD], :DWORD
- safe_attach_function :ExpandEnvironmentStringsA, [:pointer, :pointer, :DWORD], :DWORD
+ safe_attach_function :ExpandEnvironmentStringsW, %i{pointer pointer DWORD}, :DWORD
+ safe_attach_function :ExpandEnvironmentStringsA, %i{pointer pointer DWORD}, :DWORD
end
end
end
diff --git a/lib/chef/win32/api/unicode.rb b/lib/chef/win32/api/unicode.rb
index 21ddde2865..92cb3a3d66 100644
--- a/lib/chef/win32/api/unicode.rb
+++ b/lib/chef/win32/api/unicode.rb
@@ -1,6 +1,6 @@
#
# Author:: Seth Chisamore (<schisamo@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/win32/api"
+require_relative "../api"
class Chef
module ReservedNames::Win32
@@ -101,7 +101,7 @@ BOOL IsTextUnicode(
__inout LPINT lpiResult
);
=end
- safe_attach_function :IsTextUnicode, [:pointer, :int, :LPINT], :BOOL
+ safe_attach_function :IsTextUnicode, %i{pointer int LPINT}, :BOOL
=begin
int MultiByteToWideChar(
@@ -113,7 +113,7 @@ int MultiByteToWideChar(
__in int cchWideChar
);
=end
- safe_attach_function :MultiByteToWideChar, [:UINT, :DWORD, :LPCSTR, :int, :LPWSTR, :int], :int
+ safe_attach_function :MultiByteToWideChar, %i{UINT DWORD LPCSTR int LPWSTR int}, :int
=begin
int WideCharToMultiByte(
@@ -127,7 +127,7 @@ int WideCharToMultiByte(
__out LPBOOL lpUsedDefaultChar
);
=end
- safe_attach_function :WideCharToMultiByte, [:UINT, :DWORD, :LPCWSTR, :int, :LPSTR, :int, :LPCSTR, :LPBOOL], :int
+ safe_attach_function :WideCharToMultiByte, %i{UINT DWORD LPCWSTR int LPSTR int LPCSTR LPBOOL}, :int
end
end
diff --git a/lib/chef/win32/crypto.rb b/lib/chef/win32/crypto.rb
index 9832f9e67e..5521f67aee 100644
--- a/lib/chef/win32/crypto.rb
+++ b/lib/chef/win32/crypto.rb
@@ -1,6 +1,6 @@
#
# Author:: Jay Mundrawala (<jdm@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,11 +16,11 @@
# limitations under the License.
#
-require "chef/win32/error"
-require "chef/win32/api/memory"
-require "chef/win32/api/crypto"
-require "chef/win32/unicode"
-require "digest"
+require_relative "error"
+require_relative "api/memory"
+require_relative "api/crypto"
+require_relative "unicode"
+require "digest" unless defined?(Digest)
class Chef
module ReservedNames::Win32
diff --git a/lib/chef/win32/error.rb b/lib/chef/win32/error.rb
index 83d4583f1d..c2dc7785ea 100644
--- a/lib/chef/win32/error.rb
+++ b/lib/chef/win32/error.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -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_relative "api/error"
+require_relative "memory"
+require_relative "unicode"
+require_relative "../exceptions"
class Chef
module ReservedNames::Win32
@@ -50,7 +50,7 @@ class Chef
# Extract the string
begin
- return buffer.read_pointer.read_wstring(num_chars)
+ buffer.read_pointer.read_wstring(num_chars)
ensure
Chef::ReservedNames::Win32::Memory.local_free(buffer.read_pointer)
end
diff --git a/lib/chef/win32/eventlog.rb b/lib/chef/win32/eventlog.rb
index eae0ae4abf..54e9fc3077 100644
--- a/lib/chef/win32/eventlog.rb
+++ b/lib/chef/win32/eventlog.rb
@@ -1,7 +1,7 @@
#
# Author:: Jay Mundrawala (<jdm@chef.io>)
#
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,16 +16,16 @@
# limitations under the License.
#
-if Chef::Platform.windows? && (not Chef::Platform.windows_server_2003?)
- if !defined? Chef::Win32EventLogLoaded
+if ChefUtils.windows?
+ unless defined? Chef::Win32EventLogLoaded
if defined? Windows::Constants
- [:INFINITE, :WAIT_FAILED, :FORMAT_MESSAGE_IGNORE_INSERTS, :ERROR_INSUFFICIENT_BUFFER].each do |c|
+ %i{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 # rubocop:disable Style/ConstantName
+ Chef::Win32EventLogLoaded = true # rubocop:disable Naming/ConstantName
end
end
diff --git a/lib/chef/win32/file.rb b/lib/chef/win32/file.rb
index 1009f8c5a9..55fc2461e8 100644
--- a/lib/chef/win32/file.rb
+++ b/lib/chef/win32/file.rb
@@ -1,7 +1,7 @@
#
# Author:: Seth Chisamore (<schisamo@chef.io>)
-# Author:: Mark Mzyk (<mmzyk@ospcode.com>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Author:: Mark Mzyk (<mmzyk@chef.io>)
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,11 +17,12 @@
# limitations under the License.
#
-require "chef/mixin/wide_string"
-require "chef/win32/api/file"
-require "chef/win32/api/security"
-require "chef/win32/error"
-require "chef/win32/unicode"
+require_relative "../mixin/wide_string"
+require_relative "api/file"
+require_relative "api/security"
+require_relative "error"
+require_relative "unicode"
+require_relative "version"
class Chef
module ReservedNames::Win32
@@ -40,6 +41,7 @@ class Chef
#
def self.link(old_name, new_name)
raise Errno::ENOENT, "(#{old_name}, #{new_name})" unless ::File.exist?(old_name) || ::File.symlink?(old_name)
+
# TODO do a check for CreateHardLinkW and
# raise NotImplemented exception on older Windows
old_name = encode_path(old_name)
@@ -60,6 +62,7 @@ class Chef
# TODO do a check for CreateSymbolicLinkW and
# raise NotImplemented exception on older Windows
flags = ::File.directory?(old_name) ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0
+ flags |= SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE if Chef::ReservedNames::Win32::Version.new.win_10_creators_or_higher?
old_name = encode_path(old_name)
new_name = encode_path(new_name)
unless CreateSymbolicLinkW(new_name, old_name, flags)
@@ -75,7 +78,7 @@ class Chef
def self.symlink?(file_name)
is_symlink = false
path = encode_path(file_name)
- if ::File.exists?(file_name) || ::File.symlink?(file_name)
+ if ::File.exist?(file_name) || ::File.symlink?(file_name)
if (GetFileAttributesW(path) & FILE_ATTRIBUTE_REPARSE_POINT) > 0
file_search_handle(file_name) do |handle, find_data|
if find_data[:dw_reserved_0] == IO_REPARSE_TAG_SYMLINK
@@ -87,13 +90,22 @@ class Chef
is_symlink
end
+ def self.realpath(file_name)
+ if symlink?(file_name)
+ readlink(file_name)
+ else
+ file_name
+ end
+ end
+
# Returns the path of the of the symbolic link referred to by +file+.
#
# Requires Windows Vista or later. On older versions of Windows it
# will raise a NotImplementedError, as per MRI.
#
def self.readlink(link_name)
- raise Errno::ENOENT, link_name unless ::File.exists?(link_name) || ::File.symlink?(link_name)
+ raise Errno::ENOENT, link_name unless ::File.exist?(link_name) || ::File.symlink?(link_name)
+
symlink_file_handle(link_name) do |handle|
# Go to DeviceIoControl to get the symlink information
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa364571(v=vs.85).aspx
@@ -111,8 +123,8 @@ class Chef
# Return the link destination (strip off \??\ at the beginning, which is a local filesystem thing)
link_dest = reparse_buffer.reparse_buffer.substitute_name
- if link_dest =~ /^\\\?\?\\/
- link_dest = link_dest[4..-1]
+ if /^\\\?\?\\/.match?(link_dest)
+ link_dest = link_dest[4..]
end
link_dest
end
@@ -154,16 +166,6 @@ class Chef
VersionInfo.new(file_name)
end
- def self.verify_links_supported!
- begin
- CreateSymbolicLinkW(nil)
- rescue Chef::Exceptions::Win32APIFunctionNotImplemented => e
- raise e
- rescue Exception
- # things are ok.
- end
- end
-
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 |
@@ -172,7 +174,8 @@ class Chef
Chef::ReservedNames::Win32::Security::STANDARD_RIGHTS_READ
token = Chef::ReservedNames::Win32::Security.open_process_token(
Chef::ReservedNames::Win32::Process.get_current_process,
- token_rights)
+ token_rights
+ )
duplicate_token = token.duplicate_token(:SecurityImpersonation)
mapping = Chef::ReservedNames::Win32::Security::GENERIC_MAPPING.new
@@ -182,7 +185,7 @@ class Chef
mapping[:GenericAll] = Chef::ReservedNames::Win32::Security::FILE_ALL_ACCESS
Chef::ReservedNames::Win32::Security.access_check(security_descriptor, duplicate_token,
- desired_access, mapping)
+ desired_access, mapping)
end
def self.delete_volume_mount_point(mount_point)
@@ -214,5 +217,5 @@ class Chef
end
end
-require "chef/win32/file/info"
-require "chef/win32/file/version_info"
+require_relative "file/info"
+require_relative "file/version_info"
diff --git a/lib/chef/win32/file/info.rb b/lib/chef/win32/file/info.rb
index 55873f8a0b..46a0250c38 100644
--- a/lib/chef/win32/file/info.rb
+++ b/lib/chef/win32/file/info.rb
@@ -1,6 +1,6 @@
#
# Author:: Seth Chisamore (<schisamo@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/win32/file"
+require_relative "../file"
class Chef
module ReservedNames::Win32
@@ -34,6 +34,7 @@ class Chef
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa363788(v=vs.85).aspx
def initialize(file_name)
raise Errno::ENOENT, file_name unless ::File.exist?(file_name)
+
@file_info = retrieve_file_info(file_name)
end
diff --git a/lib/chef/win32/file/version_info.rb b/lib/chef/win32/file/version_info.rb
index fa04096cf1..d1b7c70543 100644
--- a/lib/chef/win32/file/version_info.rb
+++ b/lib/chef/win32/file/version_info.rb
@@ -1,6 +1,6 @@
#
# Author:: Matt Wrock (<matt@mattwrock.com>)
-# Copyright:: Copyright 2015-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) 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/win32/file"
+require_relative "../file"
class Chef
module ReservedNames::Win32
@@ -28,31 +28,32 @@ class Chef
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|
+ %i{
+ 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
+
+ get_version_info_string(method.to_s)
+ rescue Chef::Exceptions::Win32APIError
+ return nil
+
end
end
diff --git a/lib/chef/win32/handle.rb b/lib/chef/win32/handle.rb
index 3ebb6983c4..1b0257ed68 100644
--- a/lib/chef/win32/handle.rb
+++ b/lib/chef/win32/handle.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -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_relative "api/process"
+require_relative "api/psapi"
+require_relative "api/system"
+require_relative "error"
class Chef
module ReservedNames::Win32
diff --git a/lib/chef/win32/memory.rb b/lib/chef/win32/memory.rb
index 49dcdfbd41..52dcb6cfb7 100644
--- a/lib/chef/win32/memory.rb
+++ b/lib/chef/win32/memory.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/win32/error"
-require "chef/win32/api/memory"
+require_relative "error"
+require_relative "api/memory"
class Chef
module ReservedNames::Win32
@@ -35,7 +35,7 @@ class Chef
Chef::ReservedNames::Win32::Error.raise!
end
# If a block is passed, handle freeing the memory at the end
- if block != nil
+ if !block.nil?
begin
yield result
ensure
@@ -67,7 +67,7 @@ class Chef
# Free memory allocated using local_alloc
def self.local_free(pointer)
result = LocalFree(pointer)
- if !result.null?
+ unless result.null?
Chef::ReservedNames::Win32::Error.raise!
end
end
diff --git a/lib/chef/win32/mutex.rb b/lib/chef/win32/mutex.rb
index a14a160f56..85f4036c87 100644
--- a/lib/chef/win32/mutex.rb
+++ b/lib/chef/win32/mutex.rb
@@ -1,6 +1,6 @@
#
# Author:: Serdar Sutay (<serdar@chef.io>)
-# Copyright:: Copyright 2013-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/win32/api/synchronization"
-require "chef/win32/unicode"
+require_relative "api/synchronization"
+require_relative "unicode"
class Chef
module ReservedNames::Win32
@@ -55,7 +55,7 @@ class Chef
when WAIT_ABANDONED
# Previous owner of the mutex died before it can release the
# mutex. Log a warning and continue.
- Chef::Log.debug "Existing owner of the mutex exited prematurely."
+ Chef::Log.trace "Existing owner of the mutex exited prematurely."
break
when WAIT_OBJECT_0
# Mutex is successfully acquired.
@@ -68,7 +68,7 @@ class Chef
end
#####################################################
- # Releaes the mutex
+ # Releases the mutex
def release
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms685066(v=vs.85).aspx
# Note that release method needs to be called more than once
@@ -95,7 +95,7 @@ if other threads attempt to acquire the mutex.")
@handle = OpenMutexW(SYNCHRONIZE, true, name.to_wstring)
if @handle == 0
- # Mutext doesn't exist so create one.
+ # Mutex doesn't exist so create one.
# In the initial creation of the mutex initial_owner is set to
# false so that mutex will not be acquired until someone calls
# acquire.
diff --git a/lib/chef/win32/net.rb b/lib/chef/win32/net.rb
index 09db2af89d..f985891c17 100644
--- a/lib/chef/win32/net.rb
+++ b/lib/chef/win32/net.rb
@@ -1,6 +1,6 @@
#
# Author:: Jay Mundrawala(<jdm@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/win32/api/net"
-require "chef/win32/error"
-require "chef/mixin/wide_string"
+require_relative "api/net"
+require_relative "error"
+require_relative "../mixin/wide_string"
class Chef
module ReservedNames::Win32
@@ -40,6 +40,7 @@ class Chef
usri3_priv: 0,
usri3_home_dir: nil,
usri3_comment: nil,
+ # cspell:disable-next-line
usri3_flags: UF_SCRIPT | UF_DONT_EXPIRE_PASSWD | UF_NORMAL_ACCOUNT,
usri3_script_path: nil,
usri3_auth_flags: 0,
@@ -180,6 +181,21 @@ class Chef
end
end
+ def self.net_local_group_set_info(server_name, group_name, comment)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
+ comment = wstring(comment)
+
+ buf = LOCALGROUP_INFO_1.new
+ buf[:lgrpi1_name] = FFI::MemoryPointer.from_string(group_name)
+ buf[:lgrpi1_comment] = FFI::MemoryPointer.from_string(comment)
+
+ rc = NetLocalGroupSetInfo(server_name, group_name, 1, buf, nil)
+ if rc != NERR_Success
+ Chef::ReservedNames::Win32::Error.raise!(nil, rc)
+ end
+ end
+
def self.net_user_del(server_name, user_name)
server_name = wstring(server_name)
user_name = wstring(user_name)
@@ -209,7 +225,8 @@ class Chef
buf = FFI::MemoryPointer.new(LOCALGROUP_MEMBERS_INFO_3, members.size)
Array.new(members.size) do |i|
member_info = LOCALGROUP_MEMBERS_INFO_3.new(
- buf + i * LOCALGROUP_MEMBERS_INFO_3.size)
+ buf + i * LOCALGROUP_MEMBERS_INFO_3.size
+ )
member_info[:lgrmi3_domainandname] = FFI::MemoryPointer.from_string(wstring(members[i]))
member_info
end
@@ -221,7 +238,8 @@ class Chef
lgrmi3s = members_to_lgrmi3(members)
rc = NetLocalGroupAddMembers(
- server_name, group_name, 3, lgrmi3s[0], members.size)
+ server_name, group_name, 3, lgrmi3s[0], members.size
+ )
if rc != NERR_Success
Chef::ReservedNames::Win32::Error.raise!(nil, rc)
@@ -234,7 +252,8 @@ class Chef
lgrmi3s = members_to_lgrmi3(members)
rc = NetLocalGroupSetMembers(
- server_name, group_name, 3, lgrmi3s[0], members.size)
+ server_name, group_name, 3, lgrmi3s[0], members.size
+ )
if rc != NERR_Success
Chef::ReservedNames::Win32::Error.raise!(nil, rc)
@@ -247,7 +266,8 @@ class Chef
lgrmi3s = members_to_lgrmi3(members)
rc = NetLocalGroupDelMembers(
- server_name, group_name, 3, lgrmi3s[0], members.size)
+ server_name, group_name, 3, lgrmi3s[0], members.size
+ )
if rc != NERR_Success
Chef::ReservedNames::Win32::Error.raise!(nil, rc)
diff --git a/lib/chef/win32/process.rb b/lib/chef/win32/process.rb
index 76e526340b..17621f8518 100644
--- a/lib/chef/win32/process.rb
+++ b/lib/chef/win32/process.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -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_relative "api/process"
+require_relative "api/psapi"
+require_relative "error"
+require_relative "handle"
+require "ffi" unless defined?(FFI)
class Chef
module ReservedNames::Win32
@@ -82,8 +82,8 @@ class Chef
(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
+ # Must have PROCESS_QUERY_INFORMATION or PROCESS_QUERY_LIMITED_INFORMATION rights,
+ # AND the PROCESS_VM_READ right
def self.get_process_memory_info(handle)
memory_info = PROCESS_MEMORY_COUNTERS.new
unless GetProcessMemoryInfo(handle.handle, memory_info, memory_info.size)
diff --git a/lib/chef/win32/registry.rb b/lib/chef/win32/registry.rb
index 613994295c..4b5f8ede41 100644
--- a/lib/chef/win32/registry.rb
+++ b/lib/chef/win32/registry.rb
@@ -2,7 +2,7 @@
# Author:: Prajakta Purohit (<prajakta@chef.io>)
# Author:: Lamont Granquist (<lamont@chef.io>)
#
-# Copyright:: Copyright 2012-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,14 +16,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-require "chef/reserved_names"
-require "chef/win32/api"
-require "chef/mixin/wide_string"
-
-if RUBY_PLATFORM =~ /mswin|mingw32|windows/
- require "chef/monkey_patches/win32/registry"
- require "chef/win32/api/registry"
- require "win32/registry"
+require_relative "../reserved_names"
+require_relative "api"
+require_relative "../mixin/wide_string"
+
+if RUBY_PLATFORM.match?(/mswin|mingw32|windows/)
+ Win32.autoload :Registry, File.expand_path("../monkey_patches/win32/registry", __dir__)
+ require_relative "api/registry"
require "win32/api"
end
@@ -31,7 +30,7 @@ class Chef
class Win32
class Registry
- if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+ if RUBY_PLATFORM.match?(/mswin|mingw32|windows/)
include Chef::ReservedNames::Win32::API::Registry
extend Chef::ReservedNames::Win32::API::Registry
end
@@ -40,7 +39,7 @@ class Chef
extend Chef::Mixin::WideString
attr_accessor :run_context
- attr_accessor :architecture
+ attr_reader :architecture
def initialize(run_context = nil, user_architecture = :machine)
@run_context = run_context
@@ -56,37 +55,37 @@ class Chef
hive, key = get_hive_and_key(key_path)
key_exists!(key_path)
values = hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg|
- reg.map { |name, type, data| { :name => name, :type => get_name_from_type(type), :data => data } }
+ reg.map { |name, type, data| { name: name, type: get_name_from_type(type), data: data } }
end
end
def set_value(key_path, value)
data = value[:data]
data = data.to_s if value[:type] == :string
- Chef::Log.debug("Updating value #{value[:name]} in registry key #{key_path} with type #{value[:type]} and data #{data}")
+ Chef::Log.trace("Updating value #{value[:name]} in registry key #{key_path} with type #{value[:type]} and data #{data}")
key_exists!(key_path)
hive, key = get_hive_and_key(key_path)
if value_exists?(key_path, value)
if data_exists?(key_path, value)
- Chef::Log.debug("Value #{value[:name]} in registry key #{key_path} already had those values, not updated")
+ Chef::Log.trace("Value #{value[:name]} in registry key #{key_path} already had those values, not updated")
return false
else
hive.open(key, ::Win32::Registry::KEY_SET_VALUE | ::Win32::Registry::KEY_QUERY_VALUE | registry_system_architecture) do |reg|
reg.write(value[:name], get_type_from_name(value[:type]), data)
end
- Chef::Log.debug("Value #{value[:name]} in registry key #{key_path} updated")
+ Chef::Log.trace("Value #{value[:name]} in registry key #{key_path} updated")
end
else
hive.open(key, ::Win32::Registry::KEY_SET_VALUE | ::Win32::Registry::KEY_QUERY_VALUE | registry_system_architecture) do |reg|
reg.write(value[:name], get_type_from_name(value[:type]), data)
end
- Chef::Log.debug("Value #{value[:name]} in registry key #{key_path} created")
+ Chef::Log.trace("Value #{value[:name]} in registry key #{key_path} created")
end
true
end
def delete_value(key_path, value)
- Chef::Log.debug("Deleting value #{value[:name]} from registry key #{key_path}")
+ Chef::Log.trace("Deleting value #{value[:name]} from registry key #{key_path}")
if value_exists?(key_path, value)
begin
hive, key = get_hive_and_key(key_path)
@@ -95,43 +94,44 @@ class Chef
end
hive.open(key, ::Win32::Registry::KEY_SET_VALUE | registry_system_architecture) do |reg|
reg.delete_value(value[:name])
- Chef::Log.debug("Deleted value #{value[:name]} from registry key #{key_path}")
+ Chef::Log.trace("Deleted value #{value[:name]} from registry key #{key_path}")
end
else
- Chef::Log.debug("Value #{value[:name]} in registry key #{key_path} does not exist, not updated")
+ Chef::Log.trace("Value #{value[:name]} in registry key #{key_path} does not exist, not updated")
end
true
end
def create_key(key_path, recursive)
- Chef::Log.debug("Creating registry key #{key_path}")
+ Chef::Log.trace("Creating registry key #{key_path}")
if keys_missing?(key_path)
if recursive == true
- Chef::Log.debug("Registry key #{key_path} has missing subkeys, and recursive specified, creating them....")
+ Chef::Log.trace("Registry key #{key_path} has missing subkeys, and recursive specified, creating them....")
create_missing(key_path)
else
raise Chef::Exceptions::Win32RegNoRecursive, "Registry key #{key_path} has missing subkeys, and recursive not specified"
end
end
if key_exists?(key_path)
- Chef::Log.debug("Registry key #{key_path} already exists, doing nothing")
+ Chef::Log.trace("Registry key #{key_path} already exists, doing nothing")
else
hive, key = get_hive_and_key(key_path)
hive.create(key, ::Win32::Registry::KEY_WRITE | registry_system_architecture)
- Chef::Log.debug("Registry key #{key_path} created")
+ Chef::Log.trace("Registry key #{key_path} created")
end
true
end
def delete_key(key_path, recursive)
- Chef::Log.debug("Deleting registry key #{key_path}")
+ Chef::Log.trace("Deleting registry key #{key_path}")
unless key_exists?(key_path)
- Chef::Log.debug("Registry key #{key_path}, does not exist, not deleting")
+ Chef::Log.trace("Registry key #{key_path}, does not exist, not deleting")
return true
end
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
@@ -142,7 +142,7 @@ class Chef
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")
+ Chef::Log.trace("Registry key #{key_path} deleted")
true
end
@@ -153,7 +153,7 @@ class Chef
return true
end
rescue ::Win32::Registry::Error => e
- return false
+ false
end
end
@@ -161,6 +161,7 @@ class Chef
unless key_exists?(key_path)
raise Chef::Exceptions::Win32RegKeyMissing, "Registry key #{key_path} does not exist"
end
+
true
end
@@ -170,7 +171,7 @@ class Chef
rescue Chef::Exceptions::Win32RegHiveMissing => e
return false
end
- return true
+ true
end
def has_subkeys?(key_path)
@@ -179,7 +180,7 @@ class Chef
hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg|
reg.each_key { |key| return true }
end
- return false
+ false
end
def get_subkeys(key_path)
@@ -189,7 +190,7 @@ class Chef
hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg|
reg.each_key { |current_key| subkeys << current_key }
end
- return subkeys
+ subkeys
end
# 32-bit chef clients running on 64-bit machines will default to reading the 64-bit registry
@@ -204,7 +205,7 @@ class Chef
hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg|
return true if reg.any? { |val| safely_downcase(val) == safely_downcase(value[:name]) }
end
- return false
+ false
end
def data_exists?(key_path, value)
@@ -219,13 +220,14 @@ class Chef
end
end
end
- return false
+ false
end
def value_exists!(key_path, value)
unless value_exists?(key_path, value)
raise Chef::Exceptions::Win32RegValueMissing, "Registry key #{key_path} has no value named #{value[:name]}"
end
+
true
end
@@ -233,6 +235,7 @@ class Chef
unless data_exists?(key_path, value)
raise Chef::Exceptions::Win32RegDataMissing, "Registry key #{key_path} has no value named #{value[:name]}, containing type #{value[:type]} and data #{value[:data]}"
end
+
true
end
@@ -249,7 +252,7 @@ class Chef
end
end
end
- return false
+ false
end
def type_matches!(key_path, value)
@@ -279,7 +282,8 @@ class Chef
if val.is_a? String
return val.downcase
end
- return val
+
+ val
end
def node
@@ -316,18 +320,18 @@ class Chef
raise Chef::Exceptions::Win32RegHiveMissing, "Registry Hive #{hive_name} does not exist" unless hive
- return hive, key
+ [hive, key]
end
def _type_name_map
{
- :binary => ::Win32::Registry::REG_BINARY,
- :string => ::Win32::Registry::REG_SZ,
- :multi_string => ::Win32::Registry::REG_MULTI_SZ,
- :expand_string => ::Win32::Registry::REG_EXPAND_SZ,
- :dword => ::Win32::Registry::REG_DWORD,
- :dword_big_endian => ::Win32::Registry::REG_DWORD_BIG_ENDIAN,
- :qword => ::Win32::Registry::REG_QWORD,
+ binary: ::Win32::Registry::REG_BINARY,
+ string: ::Win32::Registry::REG_SZ,
+ multi_string: ::Win32::Registry::REG_MULTI_SZ,
+ expand_string: ::Win32::Registry::REG_EXPAND_SZ,
+ dword: ::Win32::Registry::REG_DWORD,
+ dword_big_endian: ::Win32::Registry::REG_DWORD_BIG_ENDIAN,
+ qword: ::Win32::Registry::REG_QWORD,
}
end
@@ -336,7 +340,7 @@ class Chef
end
def get_type_from_num(val_type)
- value = {
+ {
3 => ::Win32::Registry::REG_BINARY,
1 => ::Win32::Registry::REG_SZ,
7 => ::Win32::Registry::REG_MULTI_SZ,
@@ -345,7 +349,6 @@ class Chef
5 => ::Win32::Registry::REG_DWORD_BIG_ENDIAN,
11 => ::Win32::Registry::REG_QWORD,
}[val_type]
- return value
end
def create_missing(key_path)
@@ -356,8 +359,8 @@ class Chef
hive, key = get_hive_and_key(key_path)
missing_key_arr.each do |intermediate_key|
existing_key_path = existing_key_path << "\\" << intermediate_key
- if !key_exists?(existing_key_path)
- Chef::Log.debug("Recursively creating registry key #{existing_key_path}")
+ unless key_exists?(existing_key_path)
+ Chef::Log.trace("Recursively creating registry key #{existing_key_path}")
hive.create(get_key(existing_key_path), ::Win32::Registry::KEY_ALL_ACCESS | registry_system_architecture)
end
end
diff --git a/lib/chef/win32/security.rb b/lib/chef/win32/security.rb
index 7fc3215786..3894c65b21 100644
--- a/lib/chef/win32/security.rb
+++ b/lib/chef/win32/security.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,13 +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/mixin/wide_string"
+require_relative "api/security"
+require_relative "error"
+require_relative "memory"
+require_relative "process"
+require_relative "unicode"
+require_relative "security/token"
+require_relative "../mixin/wide_string"
class Chef
module ReservedNames::Win32
@@ -56,8 +56,8 @@ class Chef
granted_access_ptr = FFI::MemoryPointer.new(:ulong)
unless AccessCheck(security_descriptor_ptr, token_handle, rights_ptr.read_ulong,
- generic_mapping, privileges, privileges_length_ptr, granted_access_ptr,
- result_ptr)
+ generic_mapping, privileges, privileges_length_ptr, granted_access_ptr,
+ result_ptr)
Chef::ReservedNames::Win32::Error.raise!
end
result_ptr.read_ulong == 1
@@ -113,10 +113,20 @@ class Chef
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
+ test_and_raise_lsa_nt_status(result)
+ end
+ end
+
+ def self.remove_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 = LsaRemoveAccountRights(policy_handle.read_pointer, sid, false, privilege_pointer, 1)
+ test_and_raise_lsa_nt_status(result)
end
end
@@ -190,20 +200,55 @@ class Chef
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
+
+ test_and_raise_lsa_nt_status(result)
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)
+ result = LsaFreeMemory(privilege_pointer.read_pointer)
+ test_and_raise_lsa_nt_status(result)
end
privileges
end
+ def self.get_account_with_user_rights(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
+
+ buffer = FFI::MemoryPointer.new(:pointer)
+ count = FFI::MemoryPointer.new(:ulong)
+
+ accounts = []
+ with_lsa_policy(nil) do |policy_handle, sid|
+ result = LsaEnumerateAccountsWithUserRight(policy_handle.read_pointer, privilege_pointer, buffer, count)
+ if result == 0
+ win32_error = LsaNtStatusToWinError(result)
+ return [] if win32_error == 1313 # NO_SUCH_PRIVILEGE - https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--1300-1699-
+
+ test_and_raise_lsa_nt_status(result)
+
+ count.read_ulong.times do |i|
+ sid = LSA_ENUMERATION_INFORMATION.new(buffer.read_pointer + i * LSA_ENUMERATION_INFORMATION.size)
+ sid_name = lookup_account_sid(sid[:Sid])
+ domain, name, use = sid_name
+ account_name = (!domain.nil? && domain.length > 0) ? "#{domain}\\#{name}" : name
+ accounts << account_name
+ end
+ end
+
+ result = LsaFreeMemory(buffer.read_pointer)
+ test_and_raise_lsa_nt_status(result)
+ end
+
+ accounts
+ end
+
def self.get_ace(acl, index)
acl = acl.pointer if acl.respond_to?(:pointer)
ace = FFI::Buffer.new :pointer
@@ -239,7 +284,7 @@ class Chef
security_descriptor = FFI::MemoryPointer.new :pointer
hr = GetNamedSecurityInfoW(path.to_wstring, type, info, nil, nil, nil, nil, security_descriptor)
if hr != ERROR_SUCCESS
- Chef::ReservedNames::Win32::Error.raise!("get_named_security_info(#{path}, #{type}, #{info})")
+ Chef::ReservedNames::Win32::Error.raise!("get_named_security_info(#{path}, #{type}, #{info})", hr)
end
result_pointer = security_descriptor.read_pointer
@@ -318,6 +363,7 @@ class Chef
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!
@@ -333,6 +379,7 @@ class Chef
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!
@@ -341,6 +388,23 @@ class Chef
SID.new(group_result[:PrimaryGroup], group_result_storage)
end
+ def self.get_token_information_elevation_type(token)
+ token_result_size = FFI::MemoryPointer.new(:ulong)
+ if GetTokenInformation(token.handle.handle, :TokenElevationType, nil, 0, token_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
+
+ info_ptr = FFI::MemoryPointer.new(:pointer)
+ token_info_pointer = TOKEN_ELEVATION_TYPE.new info_ptr
+ token_info_length = 4
+ unless GetTokenInformation(token.handle.handle, :TokenElevationType, token_info_pointer, token_info_length, token_result_size)
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ token_info_pointer[:ElevationType]
+ end
+
def self.initialize_acl(acl_size)
acl = FFI::MemoryPointer.new acl_size
unless InitializeAcl(acl, acl_size, ACL_REVISION)
@@ -379,7 +443,7 @@ class Chef
system_name = system_name.to_wstring if system_name
if LookupAccountNameW(system_name, name.to_wstring, nil, sid_size, nil, referenced_domain_name_size, nil)
raise "Expected ERROR_INSUFFICIENT_BUFFER from LookupAccountName, and got no error!"
- elsif FFI::LastError.error != ERROR_INSUFFICIENT_BUFFER
+ elsif !([NO_ERROR, ERROR_INSUFFICIENT_BUFFER].include?(FFI::LastError.error))
Chef::ReservedNames::Win32::Error.raise!
end
@@ -525,20 +589,20 @@ class Chef
# Determine the security_information flags
security_information = 0
- security_information |= OWNER_SECURITY_INFORMATION if args.has_key?(:owner)
- security_information |= GROUP_SECURITY_INFORMATION if args.has_key?(:group)
- security_information |= DACL_SECURITY_INFORMATION if args.has_key?(:dacl)
- security_information |= SACL_SECURITY_INFORMATION if args.has_key?(:sacl)
- if args.has_key?(:dacl_inherits)
+ security_information |= OWNER_SECURITY_INFORMATION if args.key?(:owner)
+ security_information |= GROUP_SECURITY_INFORMATION if args.key?(:group)
+ security_information |= DACL_SECURITY_INFORMATION if args.key?(:dacl)
+ security_information |= SACL_SECURITY_INFORMATION if args.key?(:sacl)
+ if args.key?(:dacl_inherits)
security_information |= (args[:dacl_inherits] ? UNPROTECTED_DACL_SECURITY_INFORMATION : PROTECTED_DACL_SECURITY_INFORMATION)
end
- if args.has_key?(:sacl_inherits)
+ if args.key?(:sacl_inherits)
security_information |= (args[:sacl_inherits] ? UNPROTECTED_SACL_SECURITY_INFORMATION : PROTECTED_SACL_SECURITY_INFORMATION)
end
hr = SetNamedSecurityInfoW(path.to_wstring, type, security_information, owner, group, dacl, sacl)
if hr != ERROR_SUCCESS
- Chef::ReservedNames::Win32::Error.raise!
+ Chef::ReservedNames::Win32::Error.raise! nil, hr
end
end
@@ -551,7 +615,7 @@ class Chef
def set_security_descriptor_dacl(security_descriptor, acl, defaulted = false, present = nil)
security_descriptor = security_descriptor.pointer if security_descriptor.respond_to?(:pointer)
acl = acl.pointer if acl.respond_to?(:pointer)
- present = !security_descriptor.null? if present == nil
+ present = !security_descriptor.null? if present.nil?
unless SetSecurityDescriptorDacl(security_descriptor, present, acl, defaulted)
Chef::ReservedNames::Win32::Error.raise!
@@ -579,7 +643,7 @@ class Chef
def self.set_security_descriptor_sacl(security_descriptor, acl, defaulted = false, present = nil)
security_descriptor = security_descriptor.pointer if security_descriptor.respond_to?(:pointer)
acl = acl.pointer if acl.respond_to?(:pointer)
- present = !security_descriptor.null? if present == nil
+ present = !security_descriptor.null? if present.nil?
unless SetSecurityDescriptorSacl(security_descriptor, present, acl, defaulted)
Chef::ReservedNames::Win32::Error.raise!
@@ -587,26 +651,24 @@ class Chef
end
def self.with_lsa_policy(username)
- sid = lookup_account_name(username)[1]
+ sid = lookup_account_name(username)[1] if username
access = 0
access |= POLICY_CREATE_ACCOUNT
access |= POLICY_LOOKUP_NAMES
+ access |= POLICY_VIEW_LOCAL_INFORMATION if username.nil?
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
+ test_and_raise_lsa_nt_status(result)
+
+ sid_pointer = username.nil? ? nil : sid.pointer
begin
- yield policy_handle, sid.pointer
+ 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
+ result = LsaClose(policy_handle.read_pointer)
+ test_and_raise_lsa_nt_status(result)
end
end
@@ -627,20 +689,27 @@ class Chef
# Checks if the caller has the admin privileges in their
# security token
def self.has_admin_privileges?
- if Chef::Platform.windows_server_2003?
- # Admin privileges do not exist on Windows Server 2003
-
- true
- else
+ # a regular user doesn't have privileges to call Chef::ReservedNames::Win32::Security.OpenProcessToken
+ # hence we return false if the open_current_process_token fails with `Access is denied.` error message.
+ begin
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)
+ rescue Exception => run_error
+ return false if /Access is denied/.match?(run_error.message)
- # Assume process is not elevated if the call fails.
- # Process is elevated if the result is different than 0.
- success && (elevation_result.read_ulong != 0)
+ Chef::ReservedNames::Win32::Error.raise!
end
+
+ # display token elevation details
+ token_elevation_type = get_token_information_elevation_type(process_token)
+ Chef::Log.trace("Token Elevation Type: #{token_elevation_type}")
+
+ elevation_result = FFI::Buffer.new(:ulong)
+ elevation_result_size = FFI::MemoryPointer.new(:uint32)
+ success = GetTokenInformation(process_token.handle.handle, :TokenElevation, elevation_result, 4, elevation_result_size)
+
+ # Assume process is not elevated if the call fails.
+ # Process is elevated if the result is different than 0.
+ success && (elevation_result.read_ulong != 0)
end
def self.logon_user(username, domain, password, logon_type, logon_provider)
@@ -654,12 +723,19 @@ class Chef
end
Token.new(Handle.new(token.read_pointer))
end
+
+ def self.test_and_raise_lsa_nt_status(result)
+ win32_error = LsaNtStatusToWinError(result)
+ if win32_error != 0
+ Chef::ReservedNames::Win32::Error.raise!(nil, win32_error)
+ end
+ 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_relative "security/ace"
+require_relative "security/acl"
+require_relative "security/securable_object"
+require_relative "security/security_descriptor"
+require_relative "security/sid"
diff --git a/lib/chef/win32/security/ace.rb b/lib/chef/win32/security/ace.rb
index d593513983..945fcdfdcd 100644
--- a/lib/chef/win32/security/ace.rb
+++ b/lib/chef/win32/security/ace.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,11 +16,11 @@
# limitations under the License.
#
-require "chef/win32/security"
-require "chef/win32/security/sid"
-require "chef/win32/memory"
+require_relative "../security"
+require_relative "sid"
+require_relative "../memory"
-require "ffi"
+require "ffi" unless defined?(FFI)
class Chef
module ReservedNames::Win32
diff --git a/lib/chef/win32/security/acl.rb b/lib/chef/win32/security/acl.rb
index 8a04987e44..31838b6c68 100644
--- a/lib/chef/win32/security/acl.rb
+++ b/lib/chef/win32/security/acl.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/win32/security"
-require "chef/win32/security/ace"
-require "ffi"
+require_relative "../security"
+require_relative "ace"
+require "ffi" unless defined?(FFI)
class Chef
module ReservedNames::Win32
@@ -45,10 +45,11 @@ class Chef
def ==(other)
return false if length != other.length
+
0.upto(length - 1) do |i|
return false if self[i] != other[i]
end
- return true
+ true
end
def pointer
@@ -88,7 +89,7 @@ class Chef
end
def to_s
- "[#{self.collect { |ace| ace.to_s }.join(", ")}]"
+ "[#{collect(&:to_s).join(", ")}]"
end
def self.align_dword(size)
diff --git a/lib/chef/win32/security/securable_object.rb b/lib/chef/win32/security/securable_object.rb
index aef1a72c8c..3dd1470e9e 100644
--- a/lib/chef/win32/security/securable_object.rb
+++ b/lib/chef/win32/security/securable_object.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/win32/security"
-require "chef/win32/security/acl"
-require "chef/win32/security/sid"
+require_relative "../security"
+require_relative "acl"
+require_relative "sid"
class Chef
module ReservedNames::Win32
@@ -42,10 +42,10 @@ class Chef
# compare an existing ACE with one you want to create.
def predict_rights_mask(generic_mask)
mask = generic_mask
- #mask |= Chef::ReservedNames::Win32::API::Security::STANDARD_RIGHTS_READ if (mask | Chef::ReservedNames::Win32::API::Security::GENERIC_READ) != 0
- #mask |= Chef::ReservedNames::Win32::API::Security::STANDARD_RIGHTS_WRITE if (mask | Chef::ReservedNames::Win32::API::Security::GENERIC_WRITE) != 0
- #mask |= Chef::ReservedNames::Win32::API::Security::STANDARD_RIGHTS_EXECUTE if (mask | Chef::ReservedNames::Win32::API::Security::GENERIC_EXECUTE) != 0
- #mask |= Chef::ReservedNames::Win32::API::Security::STANDARD_RIGHTS_ALL if (mask | Chef::ReservedNames::Win32::API::Security::GENERIC_ALL) != 0
+ # mask |= Chef::ReservedNames::Win32::API::Security::STANDARD_RIGHTS_READ if (mask | Chef::ReservedNames::Win32::API::Security::GENERIC_READ) != 0
+ # mask |= Chef::ReservedNames::Win32::API::Security::STANDARD_RIGHTS_WRITE if (mask | Chef::ReservedNames::Win32::API::Security::GENERIC_WRITE) != 0
+ # mask |= Chef::ReservedNames::Win32::API::Security::STANDARD_RIGHTS_EXECUTE if (mask | Chef::ReservedNames::Win32::API::Security::GENERIC_EXECUTE) != 0
+ # mask |= Chef::ReservedNames::Win32::API::Security::STANDARD_RIGHTS_ALL if (mask | Chef::ReservedNames::Win32::API::Security::GENERIC_ALL) != 0
if type == :SE_FILE_OBJECT
mask |= Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_READ if (mask & Chef::ReservedNames::Win32::API::Security::GENERIC_READ) != 0
mask |= Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_WRITE if (mask & Chef::ReservedNames::Win32::API::Security::GENERIC_WRITE) != 0
@@ -71,36 +71,36 @@ class Chef
end
def dacl=(val)
- Security.set_named_security_info(path, type, :dacl => val)
+ Security.set_named_security_info(path, type, dacl: val)
end
# You don't set dacl_inherits without also setting dacl,
# because Windows gets angry and denies you access. So
# if you want to do that, you may as well do both at once.
def set_dacl(dacl, dacl_inherits)
- Security.set_named_security_info(path, type, :dacl => dacl, :dacl_inherits => dacl_inherits)
+ Security.set_named_security_info(path, type, dacl: dacl, dacl_inherits: dacl_inherits)
end
def group=(val)
- Security.set_named_security_info(path, type, :group => val)
+ Security.set_named_security_info(path, type, group: val)
end
def owner=(val)
# TODO to fix serious permissions problems, we may need to enable SeBackupPrivilege. But we might need it (almost) everywhere else, too.
Security.with_privileges("SeTakeOwnershipPrivilege", "SeRestorePrivilege") do
- Security.set_named_security_info(path, type, :owner => val)
+ Security.set_named_security_info(path, type, owner: val)
end
end
def sacl=(val)
Security.with_privileges("SeSecurityPrivilege") do
- Security.set_named_security_info(path, type, :sacl => val)
+ Security.set_named_security_info(path, type, sacl: val)
end
end
def set_sacl(sacl, sacl_inherits)
Security.with_privileges("SeSecurityPrivilege") do
- Security.set_named_security_info(path, type, :sacl => sacl, :sacl_inherits => sacl_inherits)
+ Security.set_named_security_info(path, type, sacl: sacl, sacl_inherits: sacl_inherits)
end
end
end
diff --git a/lib/chef/win32/security/security_descriptor.rb b/lib/chef/win32/security/security_descriptor.rb
index 8bfd8b8287..ee2d44862f 100644
--- a/lib/chef/win32/security/security_descriptor.rb
+++ b/lib/chef/win32/security/security_descriptor.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/win32/security"
-require "chef/win32/security/acl"
-require "chef/win32/security/sid"
+require_relative "../security"
+require_relative "acl"
+require_relative "sid"
class Chef
module ReservedNames::Win32
@@ -41,7 +41,8 @@ class Chef
end
def dacl
- raise "DACL not present" if !dacl_present?
+ raise "DACL not present" unless dacl_present?
+
present, acl, defaulted = Chef::ReservedNames::Win32::Security.get_security_descriptor_dacl(self)
acl
end
@@ -65,7 +66,8 @@ class Chef
end
def sacl
- raise "SACL not present" if !sacl_present?
+ raise "SACL not present" unless sacl_present?
+
Security.with_privileges("SeSecurityPrivilege") do
present, acl, defaulted = Chef::ReservedNames::Win32::Security.get_security_descriptor_sacl(self)
acl
diff --git a/lib/chef/win32/security/sid.rb b/lib/chef/win32/security/sid.rb
index f6b88c60ce..aaf3532fc4 100644
--- a/lib/chef/win32/security/sid.rb
+++ b/lib/chef/win32/security/sid.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/win32/security"
-require "chef/win32/api/net"
-require "chef/win32/api/error"
+require_relative "../security"
+require_relative "../api/net"
+require_relative "../api/error"
require "wmi-lite/wmi"
@@ -50,7 +50,7 @@ class Chef
end
def ==(other)
- other != nil && Chef::ReservedNames::Win32::Security.equal_sid(self, other)
+ !other.nil? && Chef::ReservedNames::Win32::Security.equal_sid(self, other)
end
attr_reader :pointer
@@ -59,9 +59,14 @@ class Chef
Chef::ReservedNames::Win32::Security.lookup_account_sid(self)
end
+ def account_simple_name
+ domain, name, use = account
+ name
+ end
+
def account_name
domain, name, use = account
- (domain != nil && domain.length > 0) ? "#{domain}\\#{name}" : name
+ (!domain.nil? && domain.length > 0) ? "#{domain}\\#{name}" : name
end
def size
@@ -226,25 +231,63 @@ class Chef
end
def self.None
- SID.from_account("#{::ENV['COMPUTERNAME']}\\None")
+ SID.from_account("#{::ENV["COMPUTERNAME"]}\\None")
end
def self.Administrator
- SID.from_account("#{::ENV['COMPUTERNAME']}\\#{SID.admin_account_name}")
+ SID.from_account("#{::ENV["COMPUTERNAME"]}\\#{SID.admin_account_name}")
end
def self.Guest
- SID.from_account("#{::ENV['COMPUTERNAME']}\\Guest")
+ SID.from_account("#{::ENV["COMPUTERNAME"]}\\Guest")
end
def self.current_user
- SID.from_account("#{::ENV['USERDOMAIN']}\\#{::ENV['USERNAME']}")
+ SID.from_account("#{::ENV["USERDOMAIN"]}\\#{::ENV["USERNAME"]}")
+ end
+
+ SERVICE_ACCOUNT_USERS = [self.LocalSystem,
+ self.NtLocal,
+ self.NtNetwork].flat_map do |user_type|
+ [user_type.account_simple_name.upcase,
+ user_type.account_name.upcase]
+ end.freeze
+
+ BUILT_IN_GROUPS = [self.BuiltinAdministrators,
+ self.BuiltinUsers, self.Guests].flat_map do |user_type|
+ [user_type.account_simple_name.upcase,
+ user_type.account_name.upcase]
+ end.freeze
+
+ SYSTEM_USER = SERVICE_ACCOUNT_USERS + BUILT_IN_GROUPS
+
+ # Check if the user belongs to service accounts category
+ #
+ # @return [Boolean] True or False
+ #
+ def self.service_account_user?(user)
+ SERVICE_ACCOUNT_USERS.include?(user.to_s.upcase)
+ end
+
+ # Check if the user is in builtin system group
+ #
+ # @return [Boolean] True or False
+ #
+ def self.group_user?(user)
+ BUILT_IN_GROUPS.include?(user.to_s.upcase)
+ end
+
+ # Check if the user belongs to system users category
+ #
+ # @return [Boolean] True or False
+ #
+ def self.system_user?(user)
+ SYSTEM_USER.include?(user.to_s.upcase)
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.
+ # running elevated), and is current_user otherwise.
def self.default_security_object_owner
token = Chef::ReservedNames::Win32::Security.open_current_process_token
Chef::ReservedNames::Win32::Security.get_token_information_owner(token)
@@ -278,11 +321,11 @@ class Chef
while status == ERROR_MORE_DATA
status = NetUserEnum(servername, level, filter, bufptr, prefmaxlen, entriesread, totalentries, resume_handle)
- if status == NERR_Success || status == ERROR_MORE_DATA
+ if [NERR_Success, ERROR_MORE_DATA].include?(status)
Array.new(entriesread.read_long) do |i|
user_info = USER_INFO_3.new(bufptr.read_pointer + i * USER_INFO_3.size)
# Check if the account is the Administrator account
- # RID for the Administrator account is always 500 and it's privilage is set to USER_PRIV_ADMIN
+ # RID for the Administrator account is always 500 and it's privilege is set to USER_PRIV_ADMIN
if user_info[:usri3_user_id] == 500 && user_info[:usri3_priv] == 2 # USER_PRIV_ADMIN (2) - Administrator
admin_account_name = user_info[:usri3_name].read_wstring
break
@@ -295,6 +338,7 @@ class Chef
end
raise "Can not determine the administrator account name." if admin_account_name.nil?
+
admin_account_name
end
end
diff --git a/lib/chef/win32/security/token.rb b/lib/chef/win32/security/token.rb
index 38ef03b33c..70835d0ffe 100644
--- a/lib/chef/win32/security/token.rb
+++ b/lib/chef/win32/security/token.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,10 +16,10 @@
# limitations under the License.
#
-require "chef/win32/security"
-require "chef/win32/api/security"
-require "chef/win32/unicode"
-require "ffi"
+require_relative "../security"
+require_relative "../api/security"
+require_relative "../unicode"
+require "ffi" unless defined?(FFI)
class Chef
module ReservedNames::Win32
@@ -35,7 +35,8 @@ class Chef
def enable_privileges(*privilege_names)
# Build the list of privileges we want to set
new_privileges = Chef::ReservedNames::Win32::API::Security::TOKEN_PRIVILEGES.new(
- FFI::MemoryPointer.new(Chef::ReservedNames::Win32::API::Security::TOKEN_PRIVILEGES.size_with_privileges(privilege_names.length)))
+ FFI::MemoryPointer.new(Chef::ReservedNames::Win32::API::Security::TOKEN_PRIVILEGES.size_with_privileges(privilege_names.length))
+ )
new_privileges[:PrivilegeCount] = 0
privilege_names.each do |privilege_name|
luid = Chef::ReservedNames::Win32::API::Security::LUID.new
@@ -64,6 +65,7 @@ class Chef
unless Chef::ReservedNames::Win32::API::Security.DuplicateToken(handle.handle, security_impersonation_level, duplicate_token_handle)
raise Chef::ReservedNames::Win32::Error.raise!
end
+
Token.new(Handle.new(duplicate_token_handle.read_ulong))
end
end
diff --git a/lib/chef/win32/system.rb b/lib/chef/win32/system.rb
index ec2e5d3457..a217ee984a 100755..100644
--- a/lib/chef/win32/system.rb
+++ b/lib/chef/win32/system.rb
@@ -1,6 +1,6 @@
#
# Author:: Salim Alam (<salam@chef.io>)
-# Copyright:: Copyright 2015-2016, Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require "chef/win32/api/system"
-require "chef/win32/error"
-require "ffi"
+require_relative "api/system"
+require_relative "error"
+require "ffi" unless defined?(FFI)
class Chef
module ReservedNames::Win32
diff --git a/lib/chef/win32/unicode.rb b/lib/chef/win32/unicode.rb
index d531463be0..731fe2fbd9 100644
--- a/lib/chef/win32/unicode.rb
+++ b/lib/chef/win32/unicode.rb
@@ -1,7 +1,7 @@
#
# Author:: John Keiser (<jkeiser@chef.io>)
# Author:: Seth Chisamore (<schisamo@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/mixin/wide_string"
-require "chef/win32/api/unicode"
+require_relative "../mixin/wide_string"
+require_relative "api/unicode"
class Chef
module ReservedNames::Win32
@@ -40,13 +40,19 @@ module FFI
last_char = nil
while last_char != "\000\000"
length += 1
- last_char = self.get_bytes(0, length * 2)[-2..-1]
+ last_char = get_bytes(0, length * 2)[-2..]
end
num_wchars = length
end
- wide_to_utf8(self.get_bytes(0, num_wchars * 2))
+ wide_to_utf8(get_bytes(0, num_wchars * 2))
+ end
+
+ def read_utf16string
+ offset = 0
+ offset += 2 while get_bytes(offset, 2) != "\x00\x00"
+ get_bytes(0, offset).force_encoding("utf-16le").encode("utf-8")
end
end
end
diff --git a/lib/chef/win32/version.rb b/lib/chef/win32/version.rb
index 303fe1531d..c83e52e4fc 100644
--- a/lib/chef/win32/version.rb
+++ b/lib/chef/win32/version.rb
@@ -1,6 +1,6 @@
#
# Author:: Seth Chisamore (<schisamo@chef.io>)
-# Copyright:: Copyright 2011-2016, Chef Software Inc.
+# Copyright:: Copyright (c) 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/win32/api"
-require "chef/win32/api/system"
+require_relative "api"
+require_relative "api/system"
require "wmi-lite/wmi"
class Chef
@@ -30,6 +30,8 @@ class Chef
include Chef::ReservedNames::Win32::API::Macros
include Chef::ReservedNames::Win32::API::System
+ attr_reader :major_version, :minor_version, :build_number
+
# Ruby implementation of
# http://msdn.microsoft.com/en-us/library/ms724833(v=vs.85).aspx
# http://msdn.microsoft.com/en-us/library/ms724358(v=vs.85).aspx
@@ -41,29 +43,29 @@ class Chef
private_class_method :get_system_metrics
def self.method_name_from_marketing_name(marketing_name)
- "#{marketing_name.gsub(/\s/, '_').tr('.', '_').downcase}?"
- # "#{marketing_name.gsub(/\s/, '_').gsub(//, '_').downcase}?"
+ "#{marketing_name.gsub(/\s/, "_").tr(".", "_").downcase}?"
end
private_class_method :method_name_from_marketing_name
WIN_VERSIONS = {
- "Windows 10" => { :major => 10, :minor => 0, :callable => lambda { |product_type, suite_mask| product_type == VER_NT_WORKSTATION } },
- "Windows Server 2016" => { :major => 10, :minor => 0, :callable => lambda { |product_type, suite_mask| product_type != VER_NT_WORKSTATION } },
- "Windows 8.1" => { :major => 6, :minor => 3, :callable => lambda { |product_type, suite_mask| product_type == VER_NT_WORKSTATION } },
- "Windows Server 2012 R2" => { :major => 6, :minor => 3, :callable => lambda { |product_type, suite_mask| product_type != VER_NT_WORKSTATION } },
- "Windows 8" => { :major => 6, :minor => 2, :callable => lambda { |product_type, suite_mask| product_type == VER_NT_WORKSTATION } },
- "Windows Server 2012" => { :major => 6, :minor => 2, :callable => lambda { |product_type, suite_mask| product_type != VER_NT_WORKSTATION } },
- "Windows 7" => { :major => 6, :minor => 1, :callable => lambda { |product_type, suite_mask| product_type == VER_NT_WORKSTATION } },
- "Windows Server 2008 R2" => { :major => 6, :minor => 1, :callable => lambda { |product_type, suite_mask| product_type != VER_NT_WORKSTATION } },
- "Windows Server 2008" => { :major => 6, :minor => 0, :callable => lambda { |product_type, suite_mask| product_type != VER_NT_WORKSTATION } },
- "Windows Vista" => { :major => 6, :minor => 0, :callable => lambda { |product_type, suite_mask| product_type == VER_NT_WORKSTATION } },
- "Windows Server 2003 R2" => { :major => 5, :minor => 2, :callable => lambda { |product_type, suite_mask| get_system_metrics(SM_SERVERR2) != 0 } },
- "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 Server 2019" => { major: 10, minor: 0, callable: lambda { |product_type, suite_mask, build_number| product_type != VER_NT_WORKSTATION && build_number >= 17763 } },
+ "Windows 10" => { major: 10, minor: 0, callable: lambda { |product_type, suite_mask, build_number| product_type == VER_NT_WORKSTATION } },
+ "Windows Server 2016" => { major: 10, minor: 0, callable: lambda { |product_type, suite_mask, build_number| product_type != VER_NT_WORKSTATION && build_number <= 14393 } },
+ "Windows 8.1" => { major: 6, minor: 3, callable: lambda { |product_type, suite_mask, build_number| product_type == VER_NT_WORKSTATION } },
+ "Windows Server 2012 R2" => { major: 6, minor: 3, callable: lambda { |product_type, suite_mask, build_number| product_type != VER_NT_WORKSTATION } },
+ "Windows 8" => { major: 6, minor: 2, callable: lambda { |product_type, suite_mask, build_number| product_type == VER_NT_WORKSTATION } },
+ "Windows Server 2012" => { major: 6, minor: 2, callable: lambda { |product_type, suite_mask, build_number| product_type != VER_NT_WORKSTATION } },
+ "Windows 7" => { major: 6, minor: 1, callable: lambda { |product_type, suite_mask, build_number| product_type == VER_NT_WORKSTATION } },
+ "Windows Server 2008 R2" => { major: 6, minor: 1, callable: lambda { |product_type, suite_mask, build_number| product_type != VER_NT_WORKSTATION } },
+ "Windows Server 2008" => { major: 6, minor: 0, callable: lambda { |product_type, suite_mask, build_number| product_type != VER_NT_WORKSTATION } },
+ "Windows Vista" => { major: 6, minor: 0, callable: lambda { |product_type, suite_mask, build_number| product_type == VER_NT_WORKSTATION } },
+ "Windows Server 2003 R2" => { major: 5, minor: 2, callable: lambda { |product_type, suite_mask, build_number| get_system_metrics(SM_SERVERR2) != 0 } },
+ "Windows Home Server" => { major: 5, minor: 2, callable: lambda { |product_type, suite_mask, build_number| (suite_mask & VER_SUITE_WH_SERVER) == VER_SUITE_WH_SERVER } },
+ "Windows Server 2003" => { major: 5, minor: 2, callable: lambda { |product_type, suite_mask, build_number| get_system_metrics(SM_SERVERR2) == 0 } },
+ "Windows XP" => { major: 5, minor: 1 },
+ "Windows 2000" => { major: 5, minor: 0 },
+ }.freeze
def initialize
@major_version, @minor_version, @build_number = get_version
@@ -74,18 +76,11 @@ class Chef
@sp_minor_version = ver_info[:w_service_pack_minor]
# Obtain sku information for the purpose of identifying
- # datacenter, cluster, and core skus, the latter 2 only
- # exist in releases after Windows Server 2003
- if ! Chef::Platform.windows_server_2003?
- @sku = get_product_info(@major_version, @minor_version, @sp_major_version, @sp_minor_version)
- else
- # The get_product_info API is not supported on Win2k3,
- # use an alternative to identify datacenter skus
- @sku = get_datacenter_product_info_windows_server_2003(ver_info)
- end
+ # datacenter, cluster, and core skus
+ @sku = get_product_info(@major_version, @minor_version, @sp_major_version, @sp_minor_version)
end
- marketing_names = Array.new
+ marketing_names = []
# General Windows checks
WIN_VERSIONS.each do |k, v|
@@ -93,14 +88,14 @@ class Chef
define_method(method_name) do
(@major_version == v[:major]) &&
(@minor_version == v[:minor]) &&
- (v[:callable] ? v[:callable].call(@product_type, @suite_mask) : true)
+ (v[:callable] ? v[:callable].call(@product_type, @suite_mask, @build_number) : true)
end
marketing_names << [k, method_name]
end
define_method(:marketing_name) do
marketing_names.each do |mn|
- break mn[0] if self.send(mn[1])
+ break mn[0] if send(mn[1])
end
end
@@ -114,6 +109,10 @@ class Chef
end
end
+ def win_10_creators_or_higher?
+ windows_10? && build_number >= 15063
+ end
+
private
def get_version
@@ -129,7 +128,7 @@ class Chef
# 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(&:to_i)
end
def get_version_ex
@@ -147,12 +146,6 @@ class Chef
out.get_uint(0)
end
- def get_datacenter_product_info_windows_server_2003(ver_info)
- # The intent is not to get the actual sku, just identify
- # Windows Server 2003 datacenter
- sku = (ver_info[:w_suite_mask] & VER_SUITE_DATACENTER) ? PRODUCT_DATACENTER_SERVER : 0
- end
-
end
end
end
diff --git a/lib/chef/win32_service_constants.rb b/lib/chef/win32_service_constants.rb
new file mode 100644
index 0000000000..4b5eb34327
--- /dev/null
+++ b/lib/chef/win32_service_constants.rb
@@ -0,0 +1,143 @@
+class Chef
+ module Win32ServiceConstants
+ SC_MANAGER_ALL_ACCESS = 0xF003F
+ SC_MANAGER_CREATE_SERVICE = 0x0002
+ SC_MANAGER_CONNECT = 0x0001
+ SC_MANAGER_ENUMERATE_SERVICE = 0x0004
+ SC_MANAGER_LOCK = 0x0008
+ SC_MANAGER_MODIFY_BOOT_CONFIG = 0x0020
+ SC_MANAGER_QUERY_LOCK_STATUS = 0x0010
+ SC_STATUS_PROCESS_INFO = 0
+ SC_ENUM_PROCESS_INFO = 0
+
+ # Service control action types
+ SC_ACTION_NONE = 0
+ SC_ACTION_RESTART = 1
+ SC_ACTION_REBOOT = 2
+ SC_ACTION_RUN_COMMAND = 3
+
+ # Service access rights
+ SERVICE_ALL_ACCESS = 0xF01FF
+ SERVICE_CHANGE_CONFIG = 0x0002
+ SERVICE_ENUMERATE_DEPENDENTS = 0x0008
+ SERVICE_INTERROGATE = 0x0080
+ SERVICE_PAUSE_CONTINUE = 0x0040
+ SERVICE_QUERY_CONFIG = 0x0001
+ SERVICE_QUERY_STATUS = 0x0004
+ SERVICE_START = 0x0010
+ SERVICE_STOP = 0x0020
+ SERVICE_USER_DEFINED_CONTROL = 0x0100
+
+ # Service types
+ SERVICE_KERNEL_DRIVER = 0x00000001
+ SERVICE_FILE_SYSTEM_DRIVER = 0x00000002
+ SERVICE_ADAPTER = 0x00000004
+ SERVICE_RECOGNIZER_DRIVER = 0x00000008
+ SERVICE_WIN32_OWN_PROCESS = 0x00000010
+ SERVICE_WIN32_SHARE_PROCESS = 0x00000020
+ SERVICE_WIN32 = 0x00000030
+ SERVICE_INTERACTIVE_PROCESS = 0x00000100
+ SERVICE_DRIVER = 0x0000000B
+ SERVICE_TYPE_ALL = 0x0000013F
+
+ # Error control
+ SERVICE_ERROR_IGNORE = 0x00000000
+ SERVICE_ERROR_NORMAL = 0x00000001
+ SERVICE_ERROR_SEVERE = 0x00000002
+ SERVICE_ERROR_CRITICAL = 0x00000003
+
+ # Start types
+ SERVICE_BOOT_START = 0x00000000
+ SERVICE_SYSTEM_START = 0x00000001
+ SERVICE_AUTO_START = 0x00000002
+ SERVICE_DEMAND_START = 0x00000003
+ SERVICE_DISABLED = 0x00000004
+
+ # Service control
+
+ SERVICE_CONTROL_STOP = 0x00000001
+ SERVICE_CONTROL_PAUSE = 0x00000002
+ SERVICE_CONTROL_CONTINUE = 0x00000003
+ SERVICE_CONTROL_INTERROGATE = 0x00000004
+ SERVICE_CONTROL_SHUTDOWN = 0x00000005
+ SERVICE_CONTROL_PARAMCHANGE = 0x00000006
+ SERVICE_CONTROL_NETBINDADD = 0x00000007
+ SERVICE_CONTROL_NETBINDREMOVE = 0x00000008
+ SERVICE_CONTROL_NETBINDENABLE = 0x00000009
+ SERVICE_CONTROL_NETBINDDISABLE = 0x0000000A
+ SERVICE_CONTROL_DEVICEEVENT = 0x0000000B
+ SERVICE_CONTROL_HARDWAREPROFILECHANGE = 0x0000000C
+ SERVICE_CONTROL_POWEREVENT = 0x0000000D
+ SERVICE_CONTROL_SESSIONCHANGE = 0x0000000E
+ SERVICE_CONTROL_PRESHUTDOWN = 0x0000000F
+ SERVICE_CONTROL_TIMECHANGE = 0x00000010
+ SERVICE_CONTROL_TRIGGEREVENT = 0x00000020
+
+ # Service controls accepted
+
+ SERVICE_ACCEPT_STOP = 0x00000001
+ SERVICE_ACCEPT_PAUSE_CONTINUE = 0x00000002
+ SERVICE_ACCEPT_SHUTDOWN = 0x00000004
+ SERVICE_ACCEPT_PARAMCHANGE = 0x00000008
+ SERVICE_ACCEPT_NETBINDCHANGE = 0x00000010
+ SERVICE_ACCEPT_HARDWAREPROFILECHANGE = 0x00000020
+ SERVICE_ACCEPT_POWEREVENT = 0x00000040
+ SERVICE_ACCEPT_SESSIONCHANGE = 0x00000080
+ SERVICE_ACCEPT_PRESHUTDOWN = 0x00000100
+ SERVICE_ACCEPT_TIMECHANGE = 0x00000200
+ SERVICE_ACCEPT_TRIGGEREVENT = 0x00000400
+
+ # Service states
+ SERVICE_ACTIVE = 0x00000001
+ SERVICE_INACTIVE = 0x00000002
+ SERVICE_STATE_ALL = 0x00000003
+
+ # Service current states
+ SERVICE_STOPPED = 0x00000001
+ SERVICE_START_PENDING = 0x00000002
+ SERVICE_STOP_PENDING = 0x00000003
+ SERVICE_RUNNING = 0x00000004
+ SERVICE_CONTINUE_PENDING = 0x00000005
+ SERVICE_PAUSE_PENDING = 0x00000006
+ SERVICE_PAUSED = 0x00000007
+
+ # Info levels
+ SERVICE_CONFIG_DESCRIPTION = 1
+ SERVICE_CONFIG_FAILURE_ACTIONS = 2
+ SERVICE_CONFIG_DELAYED_AUTO_START_INFO = 3
+ SERVICE_CONFIG_FAILURE_ACTIONS_FLAG = 4
+ SERVICE_CONFIG_SERVICE_SID_INFO = 5
+ SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO = 6
+ SERVICE_CONFIG_PRESHUTDOWN_INFO = 7
+
+ # Configuration
+ SERVICE_NO_CHANGE = 0xffffffff
+
+ # Misc
+
+ WAIT_OBJECT_0 = 0
+ WAIT_TIMEOUT = 0x00000102
+ INFINITE = 0xFFFFFFFF
+
+ IDLE_CONTROL_CODE = 0
+
+ DELETE = 0x00010000
+ FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
+ FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200
+
+ NO_ERROR = 0
+
+ SE_PRIVILEGE_ENABLED = 0x00000002
+ TOKEN_ADJUST_PRIVILEGES = 0x0020
+ TOKEN_QUERY = 0x0008
+
+ # Errors
+
+ ERROR_INSUFFICIENT_BUFFER = 122
+ ERROR_MORE_DATA = 234
+ ERROR_FILE_NOT_FOUND = 2
+ ERROR_RESOURCE_TYPE_NOT_FOUND = 1813
+ ERROR_RESOURCE_NAME_NOT_FOUND = 1814
+ WAIT_FAILED = 0xFFFFFFFF
+ end
+end
diff --git a/lib/chef/workstation_config_loader.rb b/lib/chef/workstation_config_loader.rb
index 97f41240f3..ce918ba861 100644
--- a/lib/chef/workstation_config_loader.rb
+++ b/lib/chef/workstation_config_loader.rb
@@ -1,6 +1,6 @@
#
# Author:: Claire McQuin (<claire@chef.io>)
-# Copyright:: Copyright 2014-2016, Chef Software, Inc.
+# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");