diff options
author | Claire McQuin <claire@getchef.com> | 2014-10-31 16:40:02 -0700 |
---|---|---|
committer | Claire McQuin <claire@getchef.com> | 2014-10-31 16:40:02 -0700 |
commit | d24a4a16a6e1d571c6f70e11340b0e1695ff92d9 (patch) | |
tree | 4bd7a0c2761db1e8b51de86ab9144676bb4526d2 | |
parent | 4996f445cc66e3ac8c5602ff86aa0091e5ffe5bd (diff) | |
parent | 48c62d5a7c0348f2db607d80cb1b56e9fe62d693 (diff) | |
download | chef-d24a4a16a6e1d571c6f70e11340b0e1695ff92d9.tar.gz |
Merge branch 'audit-mode' into runner
26 files changed, 652 insertions, 373 deletions
diff --git a/.travis.yml b/.travis.yml index 65d28d0f68..dcc93fde32 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,9 +40,10 @@ matrix: before_script: - cd kitchen-tests script: - - if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]; then bundle exec kitchen test; fi +# FIXME: we should fix centos-6 against AWS and then enable it here + - if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]; then bundle exec kitchen test ubuntu; fi after_script: - - if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]; then bundle exec kitchen destroy; fi + - if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]; then bundle exec kitchen destroy ubuntu; fi env: - KITCHEN_YAML=.kitchen.travis.yml - EC2_SSH_KEY_PATH=~/.ssh/id_aws.pem diff --git a/CHANGELOG.md b/CHANGELOG.md index a388733423..c98c58d9c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -398,3 +398,4 @@ would not share the same actions/default_action as their parent * Raise error if a guard_interpreter is specified and a block is passed to a guard (conditional) * Allow specifying a guard_interpreter after a conditional on a resource (Fixes #1943) +* Windows package type should be a symbol (Fixes #1997) diff --git a/kitchen-tests/.kitchen.travis.yml b/kitchen-tests/.kitchen.travis.yml index 4fd6a78900..66bbe23f64 100644 --- a/kitchen-tests/.kitchen.travis.yml +++ b/kitchen-tests/.kitchen.travis.yml @@ -6,18 +6,28 @@ driver_config: iam_profile_name: <%= ENV['IAM_PROFILE_NAME'] %> provisioner: - name: chef_solo + name: chef_zero github: <%= ENV['TRAVIS_REPO_SLUG'] %> branch: <%= ENV['TRAVIS_COMMIT'] %> require_chef_omnibus: true data_path: test/fixtures +# disable file provider diffs so we don't overflow travis' line limit + client_rb: + diff_disabled: true platforms: - name: ubuntu-12.04 driver_plugin: ec2 driver_config: region: "us-west-2" - availability_zone: "us-west-2b" + availability_zone: "us-west-2a" + ssh_key: <%= ENV['EC2_SSH_KEY_PATH'] %> + security_group_ids: ["travis-ci"] + - name: centos-6.4 + driver_plugin: ec2 + driver_config: + region: "us-west-2" + availability_zone: "us-west-2a" ssh_key: <%= ENV['EC2_SSH_KEY_PATH'] %> security_group_ids: ["travis-ci"] diff --git a/kitchen-tests/.kitchen.yml b/kitchen-tests/.kitchen.yml index 651a606502..775bb59378 100644 --- a/kitchen-tests/.kitchen.yml +++ b/kitchen-tests/.kitchen.yml @@ -1,14 +1,34 @@ --- +driver: + name: vagrant + customize: + cpus: 4 + memory: 2048 + provisioner: - name: chef_solo + name: chef_zero github: "opscode/chef" branch: <%= %x(git rev-parse HEAD) %> require_chef_omnibus: true data_path: test/fixtures + client_rb: + diff_disabled: true platforms: + # upstream community mysql cookbook broken on 10.04 + #- name: ubuntu-10.04 + # run_list: apt::default - name: ubuntu-12.04 - driver_plugin: vagrant + run_list: apt::default + - name: ubuntu-14.04 + run_list: apt::default + # upstream community mysql cookbook also broken on 14.10 + #- name: ubuntu-14.10 + # run_list: apt::default + - name: centos-6.4 + run_list: yum-epel::default + - name: centos-5.10 + run_list: yum-epel::default suites: - name: webapp diff --git a/kitchen-tests/cookbooks/webapp/attributes/default.rb b/kitchen-tests/cookbooks/webapp/attributes/default.rb index efe06b6549..fb33efa49e 100644 --- a/kitchen-tests/cookbooks/webapp/attributes/default.rb +++ b/kitchen-tests/cookbooks/webapp/attributes/default.rb @@ -1,7 +1,14 @@ default['apache']['remote_host_ip'] = '127.0.0.1' -default['mysql']['version'] = "5.5" - default['webapp']['database'] = 'webapp' default['webapp']['db_username'] = 'webapp' -default['webapp']['path'] = '/var/www/' +default['webapp']['path'] = '/srv/webapp' + +# XXX: apache2 cookbook 2.0.0 has bugs around changing the mpm and then attempting a graceful restart +# which fails and leaves the service down. +case node['platform'] +when "ubuntu" + if node['platform_version'].to_f >= 14.04 + default[:apache][:mpm] = 'event' + end +end diff --git a/kitchen-tests/cookbooks/webapp/recipes/default.rb b/kitchen-tests/cookbooks/webapp/recipes/default.rb index 82479e5137..839b0ad8d8 100644 --- a/kitchen-tests/cookbooks/webapp/recipes/default.rb +++ b/kitchen-tests/cookbooks/webapp/recipes/default.rb @@ -14,8 +14,11 @@ creds = Hash.new creds[item_name] = data_bag_item('passwords', item_name) end -apache_site "default" do - enable true +web_app "webapp" do + server_name 'localhost' + server_aliases [node['fqdn'], node['hostname'], 'localhost.localdomain'] + docroot node['webapp']['path'] + cookbook 'apache2' end mysql_service "default" do diff --git a/kitchen-tests/test/fixtures/platforms/centos/5.json b/kitchen-tests/test/fixtures/platforms/centos/5.json new file mode 100644 index 0000000000..9d324a2f03 --- /dev/null +++ b/kitchen-tests/test/fixtures/platforms/centos/5.json @@ -0,0 +1,14 @@ +{ + "apache": { + "package": "httpd", + "service_name": "httpd" + }, + "mysql": { + "server_package": "mysql-server", + "client_package": "mysql", + "service_name": "mysqld" + }, + "php" : { + "package": "php53" + } +} diff --git a/kitchen-tests/test/fixtures/platforms/centos/6.json b/kitchen-tests/test/fixtures/platforms/centos/6.json new file mode 100644 index 0000000000..4f74a3ed4a --- /dev/null +++ b/kitchen-tests/test/fixtures/platforms/centos/6.json @@ -0,0 +1,14 @@ +{ + "apache": { + "package": "httpd", + "service_name": "httpd" + }, + "mysql": { + "server_package": "mysql-server", + "client_package": "mysql", + "service_name": "mysqld" + }, + "php" : { + "package": "php" + } +} diff --git a/kitchen-tests/test/fixtures/platforms/ubuntu/10.04.json b/kitchen-tests/test/fixtures/platforms/ubuntu/10.04.json new file mode 100644 index 0000000000..a9677c7ca5 --- /dev/null +++ b/kitchen-tests/test/fixtures/platforms/ubuntu/10.04.json @@ -0,0 +1,14 @@ +{ + "apache": { + "package": "apache2", + "service_name": "apache2" + }, + "mysql": { + "server_package": "mysql-server-5.1", + "client_package": "mysql-client-5.1", + "service_name": "mysql" + }, + "php" : { + "package": "php5" + } +} diff --git a/kitchen-tests/test/fixtures/platforms/ubuntu/12.04.json b/kitchen-tests/test/fixtures/platforms/ubuntu/12.04.json index 5e436a3cb0..eab46db2e5 100644 --- a/kitchen-tests/test/fixtures/platforms/ubuntu/12.04.json +++ b/kitchen-tests/test/fixtures/platforms/ubuntu/12.04.json @@ -1,42 +1,14 @@ { - "apache": { - "package": "apache2", - "binary": "/usr/sbin/apache2", - "dir": "/etc/apache2", - "lib_dir": "/usr/lib/apache2", - "libexec_dir": "/usr/lib/apache2/modules", - "cache_dir": "/var/cache/apache2", - "cgibin_dir": "/usr/lib/cgi-bin", - "docroot_dir": "/var/www", - "conf": "/etc/apache2/apache2.conf", - "perl_pkg": "perl", - "log_dir": "/var/log/apache2", - "root_group": "root", - "service_name": "apache2", - "service_restart_command": "/usr/sbin/invoke-rc.d apache2 restart && sleep 1", - "service_reload_command": "/usr/sbin/invoke-rc.d apache2 reload && sleep 1", - "default_site_name": "000-default", - "default_site_enabled": false, - "default_modules": [ - "status", - "alias", - "auth_basic", - "authn_file", - "authz_core", - "authz_groupfile", - "authz_host", - "authz_user", - "autoindex", - "dir", - "env", - "mime", - "negotiation", - "setenvif" - ] - }, - "mysql": { - "server": { - "version": "5.5" - } - } + "apache": { + "package": "apache2", + "service_name": "apache2" + }, + "mysql": { + "server_package": "mysql-server-5.5", + "client_package": "mysql-client-5.5", + "service_name": "mysql" + }, + "php" : { + "package": "php5" + } } diff --git a/kitchen-tests/test/fixtures/platforms/ubuntu/14.04.json b/kitchen-tests/test/fixtures/platforms/ubuntu/14.04.json new file mode 100644 index 0000000000..eab46db2e5 --- /dev/null +++ b/kitchen-tests/test/fixtures/platforms/ubuntu/14.04.json @@ -0,0 +1,14 @@ +{ + "apache": { + "package": "apache2", + "service_name": "apache2" + }, + "mysql": { + "server_package": "mysql-server-5.5", + "client_package": "mysql-client-5.5", + "service_name": "mysql" + }, + "php" : { + "package": "php5" + } +} diff --git a/kitchen-tests/test/fixtures/platforms/ubuntu/14.10.json b/kitchen-tests/test/fixtures/platforms/ubuntu/14.10.json new file mode 100644 index 0000000000..eab46db2e5 --- /dev/null +++ b/kitchen-tests/test/fixtures/platforms/ubuntu/14.10.json @@ -0,0 +1,14 @@ +{ + "apache": { + "package": "apache2", + "service_name": "apache2" + }, + "mysql": { + "server_package": "mysql-server-5.5", + "client_package": "mysql-client-5.5", + "service_name": "mysql" + }, + "php" : { + "package": "php5" + } +} diff --git a/kitchen-tests/test/fixtures/serverspec_helper.rb b/kitchen-tests/test/fixtures/serverspec_helper.rb index 48963dc45d..ad1f866775 100644 --- a/kitchen-tests/test/fixtures/serverspec_helper.rb +++ b/kitchen-tests/test/fixtures/serverspec_helper.rb @@ -1,4 +1,4 @@ -# Shamelessly copied from opscode/onehealth-cookbooks/apache2/test/fixtures/serverspec_helper.rb +# Shamelessly copied from https://github.com/onehealth-cookbooks/apache2/blob/master/test/fixtures/serverspec_helper.rb # The commented-out platforms in the osmapping hash can be added once we have added them into # our .kitchen.yml and .kitchen.travis.yml and added the appropriate JSON under test/fixtures/platforms. diff --git a/kitchen-tests/test/integration/webapp/serverspec/localhost/default_spec.rb b/kitchen-tests/test/integration/webapp/serverspec/localhost/default_spec.rb index 05da3ff337..64c9121a6f 100644 --- a/kitchen-tests/test/integration/webapp/serverspec/localhost/default_spec.rb +++ b/kitchen-tests/test/integration/webapp/serverspec/localhost/default_spec.rb @@ -19,21 +19,21 @@ describe "webapp::default", :end_to_end => true do end end - describe "mysql-server-#{property[:mysql][:server][:version]} package" do + describe "#{property[:mysql][:server_package]} package" do include_examples "a package" do - let(:package_name) { "mysql-server-#{property[:mysql][:server][:version]}" } + let(:package_name) { property[:mysql][:server_package] } end end - describe "mysql-client-5.5 package" do + describe "#{property[:mysql][:client_package]} package" do include_examples "a package" do - let(:package_name) { "mysql-client-5.5" } + let(:package_name) { property[:mysql][:client_package] } end end describe "php package" do include_examples "a package" do - let(:package_name) { "php5" } + let(:package_name) { property[:php][:package] } end end end @@ -57,7 +57,7 @@ describe "webapp::default", :end_to_end => true do describe "mysql service" do include_examples "a service" do - let(:service_name) { "mysql" } + let(:service_name) { property[:mysql][:service_name] } end end diff --git a/lib/chef/application.rb b/lib/chef/application.rb index abcc81c290..a2718e7556 100644 --- a/lib/chef/application.rb +++ b/lib/chef/application.rb @@ -28,370 +28,372 @@ require 'mixlib/cli' require 'tmpdir' require 'rbconfig' -class Chef::Application - include Mixlib::CLI +class Chef + class Application + include Mixlib::CLI - def initialize - super + def initialize + super - @chef_client = nil - @chef_client_json = nil - - # Always switch to a readable directory. Keeps subsequent Dir.chdir() {} - # from failing due to permissions when launched as a less privileged user. - end - - # Reconfigure the application. You'll want to override and super this method. - def reconfigure - configure_chef - configure_logging - configure_proxy_environment_variables - configure_encoding - end + @chef_client = nil + @chef_client_json = nil - # Get this party started - def run - setup_signal_handlers - reconfigure - setup_application - run_application - end + # Always switch to a readable directory. Keeps subsequent Dir.chdir() {} + # from failing due to permissions when launched as a less privileged user. + end - def setup_signal_handlers - trap("INT") do - Chef::Application.fatal!("SIGINT received, stopping", 2) + # Reconfigure the application. You'll want to override and super this method. + def reconfigure + configure_chef + configure_logging + configure_proxy_environment_variables + configure_encoding end - trap("TERM") do - Chef::Application.fatal!("SIGTERM received, stopping", 3) + # Get this party started + def run + setup_signal_handlers + reconfigure + setup_application + run_application end - unless Chef::Platform.windows? - trap("QUIT") do - Chef::Log.info("SIGQUIT received, call stack:\n " + caller.join("\n ")) + def setup_signal_handlers + trap("INT") do + Chef::Application.fatal!("SIGINT received, stopping", 2) end - trap("HUP") do - Chef::Log.info("SIGHUP received, reconfiguring") - reconfigure + trap("TERM") do + Chef::Application.fatal!("SIGTERM received, stopping", 3) + end + + unless Chef::Platform.windows? + trap("QUIT") do + Chef::Log.info("SIGQUIT received, call stack:\n " + caller.join("\n ")) + end + + trap("HUP") do + Chef::Log.info("SIGHUP received, reconfiguring") + reconfigure + end end end - end - # Parse configuration (options and config file) - def configure_chef - parse_options - load_config_file - end + # Parse configuration (options and config file) + def configure_chef + parse_options + load_config_file + end - # Parse the config file - def load_config_file - config_fetcher = Chef::ConfigFetcher.new(config[:config_file]) - if config[:config_file].nil? - Chef::Log.warn("No config file found or specified on command line, using command line options.") - elsif config_fetcher.config_missing? - pp config_missing: true - Chef::Log.warn("*****************************************") - Chef::Log.warn("Did not find config file: #{config[:config_file]}, using command line options.") - Chef::Log.warn("*****************************************") - else - config_content = config_fetcher.read_config - apply_config(config_content, config[:config_file]) + # Parse the config file + def load_config_file + config_fetcher = Chef::ConfigFetcher.new(config[:config_file]) + if config[:config_file].nil? + Chef::Log.warn("No config file found or specified on command line, using command line options.") + elsif config_fetcher.config_missing? + pp config_missing: true + Chef::Log.warn("*****************************************") + Chef::Log.warn("Did not find config file: #{config[:config_file]}, using command line options.") + Chef::Log.warn("*****************************************") + else + config_content = config_fetcher.read_config + apply_config(config_content, config[:config_file]) + end + Chef::Config.merge!(config) end - Chef::Config.merge!(config) - 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 - Chef::Log.init(MonoLogger.new(Chef::Config[:log_location])) - if want_additional_logger? - configure_stdout_logger + # Initialize and configure the logger. + # === Loggers and Formatters + # In Chef 10.x and previous, the Logger was the primary/only way that Chef + # communicated information to the user. In Chef 10.14, a new system, "output + # formatters" was added, and in Chef 11.0+ it is the default when running + # chef in a console (detected by `STDOUT.tty?`). Because output formatters + # are more complex than the logger system and users have less experience with + # them, the config option `force_logger` is provided to restore the Chef 10.x + # behavior. + # + # Conversely, for users who want formatter output even when chef is running + # unattended, the `force_formatter` option is provided. + # + # === Auto Log Level + # When `log_level` is set to `:auto` (default), the log level will be `:warn` + # when the primary output mode is an output formatter (see + # +using_output_formatter?+) and `:info` otherwise. + # + # === Automatic STDOUT Logging + # When `force_logger` is configured (e.g., Chef 10 mode), a second logger + # with output on STDOUT is added when running in a console (STDOUT is a tty) + # and the configured log_location isn't STDOUT. This accounts for the case + # that a user has configured a log_location in client.rb, but is running + # chef-client by hand to troubleshoot a problem. + def configure_logging + Chef::Log.init(MonoLogger.new(Chef::Config[:log_location])) + if want_additional_logger? + configure_stdout_logger + end + Chef::Log.level = resolve_log_level + rescue StandardError => error + Chef::Log.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", 2) end - Chef::Log.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})") - Chef::Application.fatal!("Aborting due to invalid 'log_location' configuration", 2) - end - def configure_stdout_logger - stdout_logger = MonoLogger.new(STDOUT) - stdout_logger.formatter = Chef::Log.logger.formatter - Chef::Log.loggers << stdout_logger - 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]) - end + # Based on config and whether or not STDOUT is a tty, should we setup a + # secondary logger for stdout? + def want_additional_logger? + ( Chef::Config[:log_location] != STDOUT ) && STDOUT.tty? && (!Chef::Config[:daemonize]) && (Chef::Config[:force_logger]) + end - # Use of output formatters is assumed if `force_formatter` is set or if - # `force_logger` is not set and STDOUT is to a console (tty) - def using_output_formatter? - Chef::Config[:force_formatter] || (!Chef::Config[:force_logger] && STDOUT.tty?) - end + # Use of output formatters is assumed if `force_formatter` is set or if + # `force_logger` is not set and STDOUT is to a console (tty) + def using_output_formatter? + Chef::Config[:force_formatter] || (!Chef::Config[:force_logger] && STDOUT.tty?) + end - def auto_log_level? - Chef::Config[:log_level] == :auto - end + def auto_log_level? + Chef::Config[:log_level] == :auto + end - # if log_level is `:auto`, convert it to :warn (when using output formatter) - # or :info (no output formatter). See also +using_output_formatter?+ - def resolve_log_level - if auto_log_level? - if using_output_formatter? - :warn + # if log_level is `:auto`, convert it to :warn (when using output formatter) + # or :info (no output formatter). See also +using_output_formatter?+ + def resolve_log_level + if auto_log_level? + if using_output_formatter? + :warn + else + :info + end else - :info + Chef::Config[:log_level] end - else - Chef::Config[:log_level] end - end - # Configure and set any proxy environment variables according to the config. - def configure_proxy_environment_variables - configure_http_proxy - configure_https_proxy - configure_ftp_proxy - configure_no_proxy - end - - # Sets the default external encoding to UTF-8 (users can change this, but they shouldn't) - def configure_encoding - Encoding.default_external = Chef::Config[:ruby_encoding] - end + # Configure and set any proxy environment variables according to the config. + def configure_proxy_environment_variables + configure_http_proxy + configure_https_proxy + configure_ftp_proxy + configure_no_proxy + end - # Called prior to starting the application, by the run method - def setup_application - raise Chef::Exceptions::Application, "#{self.to_s}: you must override setup_application" - 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] + end - # Actually run the application - def run_application - raise Chef::Exceptions::Application, "#{self.to_s}: you must override run_application" - end + # Called prior to starting the application, by the run method + def setup_application + raise Chef::Exceptions::Application, "#{self.to_s}: you must override setup_application" + end - # Initializes Chef::Client instance and runs it - def run_chef_client(specific_recipes = []) - Chef::LocalMode.with_server_connectivity do - override_runlist = config[:override_runlist] - if specific_recipes.size > 0 - override_runlist ||= [] - end - @chef_client = Chef::Client.new( - @chef_client_json, - :override_runlist => config[:override_runlist], - :specific_recipes => specific_recipes, - :runlist => config[:runlist] - ) - @chef_client_json = nil + # Actually run the application + def run_application + raise Chef::Exceptions::Application, "#{self.to_s}: you must override run_application" + end - if can_fork? - fork_chef_client # allowed to run client in forked process - else - # Unforked interval runs are disabled, so this runs chef-client - # once and then exits. If TERM signal is received, will "ignore" - # the signal to finish converge. - run_with_graceful_exit_option + # Initializes Chef::Client instance and runs it + def run_chef_client(specific_recipes = []) + Chef::LocalMode.with_server_connectivity do + override_runlist = config[:override_runlist] + if specific_recipes.size > 0 + override_runlist ||= [] + end + @chef_client = Chef::Client.new( + @chef_client_json, + :override_runlist => config[:override_runlist], + :specific_recipes => specific_recipes, + :runlist => config[:runlist] + ) + @chef_client_json = nil + + if can_fork? + fork_chef_client # allowed to run client in forked process + else + # Unforked interval runs are disabled, so this runs chef-client + # once and then exits. If TERM signal is received, will "ignore" + # the signal to finish converge. + run_with_graceful_exit_option + end + @chef_client = nil end - @chef_client = nil end - end - private - def can_fork? - # win32-process gem exposes some form of :fork for Process - # class. So we are seperately ensuring that the platform we're - # running on is not windows before forking. - Chef::Config[:client_fork] && Process.respond_to?(:fork) && !Chef::Platform.windows? - end - - # Run chef-client once and then exit. If TERM signal is received, ignores the - # signal to finish the converge and exists. - def run_with_graceful_exit_option - # Override the TERM signal. - trap('TERM') do - Chef::Log.debug("SIGTERM received during converge," + - " finishing converge to exit normally (send SIGINT to terminate immediately)") + private + def can_fork? + # win32-process gem exposes some form of :fork for Process + # class. So we are seperately ensuring that the platform we're + # running on is not windows before forking. + Chef::Config[:client_fork] && Process.respond_to?(:fork) && !Chef::Platform.windows? end - @chef_client.run - true - end - - def fork_chef_client - Chef::Log.info "Forking chef instance to converge..." - pid = fork do - # Want to allow forked processes to finish converging when - # TERM singal is received (exit gracefully) + # Run chef-client once and then exit. If TERM signal is received, ignores the + # signal to finish the converge and exists. + def run_with_graceful_exit_option + # Override the TERM signal. trap('TERM') do Chef::Log.debug("SIGTERM received during converge," + - " finishing converge to exit normally (send SIGINT to terminate immediately)") + " finishing converge to exit normally (send SIGINT to terminate immediately)") end - client_solo = Chef::Config[:solo] ? "chef-solo" : "chef-client" - $0 = "#{client_solo} worker: ppid=#{Process.ppid};start=#{Time.new.strftime("%R:%S")};" - begin - Chef::Log.debug "Forked instance now converging" - @chef_client.run - rescue Exception => e - Chef::Log.error(e.to_s) - exit 1 - else - exit 0 + @chef_client.run + true + end + + def fork_chef_client + Chef::Log.info "Forking chef 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," + + " finishing converge to exit normally (send SIGINT to terminate immediately)") + end + + client_solo = Chef::Config[:solo] ? "chef-solo" : "chef-client" + $0 = "#{client_solo} worker: ppid=#{Process.ppid};start=#{Time.new.strftime("%R:%S")};" + begin + Chef::Log.debug "Forked instance now converging" + @chef_client.run + rescue Exception => e + Chef::Log.error(e.to_s) + exit 1 + else + exit 0 + end end + Chef::Log.debug "Fork successful. Waiting for new chef pid: #{pid}" + result = Process.waitpid2(pid) + handle_child_exit(result) + Chef::Log.debug "Forked instance successfully reaped (pid: #{pid})" + true end - Chef::Log.debug "Fork successful. Waiting for new chef pid: #{pid}" - result = Process.waitpid2(pid) - handle_child_exit(result) - Chef::Log.debug "Forked instance successfully reaped (pid: #{pid})" - true - end - def handle_child_exit(pid_and_status) - status = pid_and_status[1] - return true if status.success? - message = if status.signaled? - "Chef run process terminated by signal #{status.termsig} (#{Signal.list.invert[status.termsig]})" - else - "Chef run process exited unsuccessfully (exit code #{status.exitstatus})" + def handle_child_exit(pid_and_status) + status = pid_and_status[1] + return true if status.success? + message = if status.signaled? + "Chef run process terminated by signal #{status.termsig} (#{Signal.list.invert[status.termsig]})" + else + "Chef run process exited unsuccessfully (exit code #{status.exitstatus})" + end + raise Exceptions::ChildConvergeError, message end - raise Exceptions::ChildConvergeError, message - end - def apply_config(config_content, config_file_path) - Chef::Config.from_string(config_content, config_file_path) - rescue Exception => error - Chef::Log.fatal("Configuration error #{error.class}: #{error.message}") - filtered_trace = error.backtrace.grep(/#{Regexp.escape(config_file_path)}/) - filtered_trace.each {|line| Chef::Log.fatal(" " + line )} - Chef::Application.fatal!("Aborting due to error in '#{config_file_path}'", 2) - end + def apply_config(config_content, config_file_path) + Chef::Config.from_string(config_content, config_file_path) + rescue Exception => error + Chef::Log.fatal("Configuration error #{error.class}: #{error.message}") + filtered_trace = error.backtrace.grep(/#{Regexp.escape(config_file_path)}/) + filtered_trace.each {|line| Chef::Log.fatal(" " + line )} + Chef::Application.fatal!("Aborting due to error in '#{config_file_path}'", 2) + end - # Set ENV['http_proxy'] - def configure_http_proxy - if http_proxy = Chef::Config[:http_proxy] - http_proxy_string = configure_proxy("http", http_proxy, - Chef::Config[:http_proxy_user], Chef::Config[:http_proxy_pass]) - env['http_proxy'] = http_proxy_string unless env['http_proxy'] - env['HTTP_PROXY'] = http_proxy_string unless env['HTTP_PROXY'] + # Set ENV['http_proxy'] + def configure_http_proxy + if http_proxy = Chef::Config[:http_proxy] + http_proxy_string = configure_proxy("http", http_proxy, + Chef::Config[:http_proxy_user], Chef::Config[:http_proxy_pass]) + env['http_proxy'] = http_proxy_string unless env['http_proxy'] + env['HTTP_PROXY'] = http_proxy_string unless env['HTTP_PROXY'] + end end - end - # Set ENV['https_proxy'] - def configure_https_proxy - if https_proxy = Chef::Config[:https_proxy] - https_proxy_string = configure_proxy("https", https_proxy, - Chef::Config[:https_proxy_user], Chef::Config[:https_proxy_pass]) - env['https_proxy'] = https_proxy_string unless env['https_proxy'] - env['HTTPS_PROXY'] = https_proxy_string unless env['HTTPS_PROXY'] + # Set ENV['https_proxy'] + def configure_https_proxy + if https_proxy = Chef::Config[:https_proxy] + https_proxy_string = configure_proxy("https", https_proxy, + Chef::Config[:https_proxy_user], Chef::Config[:https_proxy_pass]) + env['https_proxy'] = https_proxy_string unless env['https_proxy'] + env['HTTPS_PROXY'] = https_proxy_string unless env['HTTPS_PROXY'] + end end - end - # Set ENV['ftp_proxy'] - def configure_ftp_proxy - if ftp_proxy = Chef::Config[:ftp_proxy] - ftp_proxy_string = configure_proxy("ftp", ftp_proxy, - Chef::Config[:ftp_proxy_user], Chef::Config[:ftp_proxy_pass]) - env['ftp_proxy'] = ftp_proxy_string unless env['ftp_proxy'] - env['FTP_PROXY'] = ftp_proxy_string unless env['FTP_PROXY'] + # Set ENV['ftp_proxy'] + def configure_ftp_proxy + if ftp_proxy = Chef::Config[:ftp_proxy] + ftp_proxy_string = configure_proxy("ftp", ftp_proxy, + Chef::Config[:ftp_proxy_user], Chef::Config[:ftp_proxy_pass]) + env['ftp_proxy'] = ftp_proxy_string unless env['ftp_proxy'] + env['FTP_PROXY'] = ftp_proxy_string unless env['FTP_PROXY'] + end end - end - # Set ENV['no_proxy'] - def configure_no_proxy - if Chef::Config[:no_proxy] - env['no_proxy'] = Chef::Config[:no_proxy] unless env['no_proxy'] - env['NO_PROXY'] = Chef::Config[:no_proxy] unless env['NO_PROXY'] + # Set ENV['no_proxy'] + def configure_no_proxy + if Chef::Config[:no_proxy] + env['no_proxy'] = Chef::Config[:no_proxy] unless env['no_proxy'] + env['NO_PROXY'] = Chef::Config[:no_proxy] unless env['NO_PROXY'] + end end - end - # Builds a proxy uri. Examples: - # http://username:password@hostname:port - # https://username@hostname:port - # ftp://hostname:port - # when - # scheme = "http", "https", or "ftp" - # hostport = hostname:port - # user = username - # pass = password - def configure_proxy(scheme, path, user, pass) - begin - path = "#{scheme}://#{path}" unless path.include?('://') - # URI.split returns the following parts: - # [scheme, userinfo, host, port, registry, path, opaque, query, fragment] - parts = URI.split(URI.encode(path)) - # URI::Generic.build requires an integer for the port, but URI::split gives - # returns a string for the port. - parts[3] = parts[3].to_i if parts[3] - if user - userinfo = URI.encode(URI.encode(user), '@:') - if pass - userinfo << ":#{URI.encode(URI.encode(pass), '@:')}" + # Builds a proxy uri. Examples: + # http://username:password@hostname:port + # https://username@hostname:port + # ftp://hostname:port + # when + # scheme = "http", "https", or "ftp" + # hostport = hostname:port + # user = username + # pass = password + def configure_proxy(scheme, path, user, pass) + begin + path = "#{scheme}://#{path}" unless path.include?('://') + # URI.split returns the following parts: + # [scheme, userinfo, host, port, registry, path, opaque, query, fragment] + parts = URI.split(URI.encode(path)) + # URI::Generic.build requires an integer for the port, but URI::split gives + # returns a string for the port. + parts[3] = parts[3].to_i if parts[3] + if user + userinfo = URI.encode(URI.encode(user), '@:') + if pass + userinfo << ":#{URI.encode(URI.encode(pass), '@:')}" + end + parts[1] = userinfo end - parts[1] = userinfo + + return URI::Generic.build(parts).to_s + rescue URI::Error => e + # URI::Error messages generally include the offending string. Including a message + # for which proxy config item has the issue should help deduce the issue when + # the URI::Error message is vague. + raise Chef::Exceptions::BadProxyURI, "Cannot configure #{scheme} proxy. Does not comply with URI scheme. #{e.message}" end + end - return URI::Generic.build(parts).to_s - rescue URI::Error => e - # URI::Error messages generally include the offending string. Including a message - # for which proxy config item has the issue should help deduce the issue when - # the URI::Error message is vague. - raise Chef::Exceptions::BadProxyURI, "Cannot configure #{scheme} proxy. Does not comply with URI scheme. #{e.message}" + # This is a hook for testing + def env + ENV end - end - # This is a hook for testing - def env - ENV - end + class << self + def debug_stacktrace(e) + message = "#{e.class}: #{e}\n#{e.backtrace.join("\n")}" + chef_stacktrace_out = "Generated at #{Time.now.to_s}\n" + chef_stacktrace_out += message - class << self - def debug_stacktrace(e) - message = "#{e.class}: #{e}\n#{e.backtrace.join("\n")}" - chef_stacktrace_out = "Generated at #{Time.now.to_s}\n" - chef_stacktrace_out += 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.debug(message) + true + end - Chef::FileCache.store("chef-stacktrace.out", chef_stacktrace_out) - Chef::Log.fatal("Stacktrace dumped to #{Chef::FileCache.load("chef-stacktrace.out", false)}") - Chef::Log.debug(message) - true - end + # Log a fatal error message to both STDERR and the Logger, exit the application + def fatal!(msg, err = -1) + Chef::Log.fatal(msg) + Process.exit err + end - # Log a fatal error message to both STDERR and the Logger, exit the application - def fatal!(msg, err = -1) - Chef::Log.fatal(msg) - Process.exit err + def exit!(msg, err = -1) + Chef::Log.debug(msg) + Process.exit err + end end - def exit!(msg, err = -1) - Chef::Log.debug(msg) - Process.exit err - end end - end diff --git a/lib/chef/audit/chef_json_formatter.rb b/lib/chef/audit/chef_json_formatter.rb new file mode 100644 index 0000000000..3c164120b4 --- /dev/null +++ b/lib/chef/audit/chef_json_formatter.rb @@ -0,0 +1,88 @@ +RSpec::Support.require_rspec_core "formatters/base_formatter" +require 'control_group_data' +require 'ffi_yajl' + +class Chef + class Audit + class ChefJsonFormatter < ::RSpec::Core::Formatters::BaseFormatter + ::RSpec::Core::Formatters.register self, :example_group_started, :message, :stop, :close, :example_failed + + attr_reader :control_group_data + + # TODO hopefully the runner can take care of this for us since there won't be an outer-most + # control group + @@outer_example_group_found = false + + def initialize(output) + super + end + + # Invoked for each `control`, `describe`, `context` block + def example_group_started(notification) + unless @@outer_example_group_found + @control_group_data = ControlGroupData.new(notification.group.description) + @@outer_example_group_found = true + end + end + + def example_failed(notification) + e = notification.example.metadata[:execution_result].exception + raise e unless e.kind_of? ::RSpec::Expectations::ExpectationNotMetError + end + + def message(notification) + puts "message: #{notification}" + end + + def stop(notification) + notification.examples.each do |example| + control_data = build_control_from(example) + e = example.exception + if e + control = control_group_data.example_failure(e.message, control_data) + else + control = control_group_data.example_success(control_data) + end + control.line_number = example.metadata[:line_number] + end + end + + def close(notification) + output.write FFI_Yajl::Encoder.encode(control_group_data.to_hash, pretty: true) + output.close if IO === output && output != $stdout + end + + private + + def build_control_from(example) + described_class = example.metadata[:described_class] + if described_class + resource_type = described_class.class.name.split(':')[-1] + # TODO submit github PR to expose this + resource_name = described_class.instance_variable_get(:@name) + end + + 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] + while !group.nil? + describe_groups.unshift(group[:description]) + group = group[:parent_example_group] + end + # TODO remove this when we're no longer wrapping everything with "mysql audit" + describe_groups.shift + + { + :name => example.description, + :desc => example.full_description, + :resource_type => resource_type, + :resource_name => resource_name, + :context => describe_groups + } + end + + end + end +end diff --git a/lib/chef/audit/control_group_data.rb b/lib/chef/audit/control_group_data.rb new file mode 100644 index 0000000000..93abfb3c21 --- /dev/null +++ b/lib/chef/audit/control_group_data.rb @@ -0,0 +1,84 @@ +class Chef + class Audit + class ControlGroupData + attr_reader :name, :status, :number_success, :number_failed, :controls + + def initialize(name) + @status = "success" + @controls = [] + @number_success = 0 + @number_failed = 0 + @name = name + end + + + def example_success(opts={}) + @number_success += 1 + control = create_control(opts) + controls << control + control + end + + def example_failure(details=nil, opts={}) + @number_failed += 1 + @status = "failure" + control = create_control(opts) + control.details = details if details + control.status = "failure" + controls << control + control + end + + def to_hash + controls.sort! {|x,y| x.line_number <=> y.line_number} + { + :control_group => { + :name => name, + :status => status, + :number_success => number_success, + :number_failed => number_failed, + :controls => controls.collect { |c| c.to_hash } + } + } + end + + private + + def create_control(opts={}) + name = opts[:name] + resource_type = opts[:resource_type] + resource_name = opts[:resource_name] + context = opts[:context] + ControlData.new(name, resource_type, resource_name, context) + end + + end + + class ControlData + attr_reader :name, :resource_type, :resource_name, :context + attr_accessor :status, :details + # TODO this only helps with debugging + attr_accessor :line_number + + def initialize(name, resource_type, resource_name, context) + @context = context + @name = name + @resource_type = resource_type + @resource_name = resource_name + end + + def to_hash + ret = { + :name => name, + :status => status, + :details => details, + :resource_type => resource_type, + :resource_name => resource_name + } + ret[:context] = context || [] + ret + end + end + + end +end diff --git a/lib/chef/chef_fs/file_system/base_fs_dir.rb b/lib/chef/chef_fs/file_system/base_fs_dir.rb index 74038f481b..8cc277facc 100644 --- a/lib/chef/chef_fs/file_system/base_fs_dir.rb +++ b/lib/chef/chef_fs/file_system/base_fs_dir.rb @@ -40,6 +40,11 @@ class Chef true end + # An empty children array is an empty dir + def empty? + children.empty? + end + # Abstract: children end end diff --git a/lib/chef/provider/service/aix.rb b/lib/chef/provider/service/aix.rb index 6f70f797b9..0aef62c62e 100644 --- a/lib/chef/provider/service/aix.rb +++ b/lib/chef/provider/service/aix.rb @@ -24,6 +24,8 @@ class Chef class Aix < Chef::Provider::Service attr_reader :status_load_success + provides :service, os: "aix" + def initialize(new_resource, run_context) super end diff --git a/lib/chef/resource/windows_package.rb b/lib/chef/resource/windows_package.rb index c563ba5fdc..9bf3443423 100644 --- a/lib/chef/resource/windows_package.rb +++ b/lib/chef/resource/windows_package.rb @@ -43,7 +43,7 @@ class Chef set_or_return( :installer_type, arg, - :kind_of => [ String ] + :kind_of => [ Symbol ] ) end diff --git a/spec/functional/resource/base.rb b/spec/functional/resource/base.rb index cdb52fbc1b..10b26f924f 100644 --- a/spec/functional/resource/base.rb +++ b/spec/functional/resource/base.rb @@ -22,6 +22,7 @@ def run_context node = Chef::Node.new node.default[:platform] = ohai[:platform] node.default[:platform_version] = ohai[:platform_version] + node.default[:os] = ohai[:os] events = Chef::EventDispatch::Dispatcher.new Chef::RunContext.new(node, {}, events) end diff --git a/spec/functional/resource/cron_spec.rb b/spec/functional/resource/cron_spec.rb index 287b5a1391..ed30756583 100644 --- a/spec/functional/resource/cron_spec.rb +++ b/spec/functional/resource/cron_spec.rb @@ -56,8 +56,8 @@ describe Chef::Resource::Cron, :requires_root, :unix_only do let(:new_resource) do new_resource = Chef::Resource::Cron.new("Chef functional test cron", run_context) new_resource.user 'root' - # @hourly is not supported on solaris - if ohai[:platform] == "solaris" || ohai[:platform] == "solaris2" + # @hourly is not supported on solaris, aix + if ohai[:platform] == "solaris" || ohai[:platform] == "solaris2" || ohai[:platform] == "aix" new_resource.minute "0 * * * *" else new_resource.minute '@hourly' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b1d7cdbd64..cc5ba8c3ac 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -98,7 +98,7 @@ RSpec.configure do |config| config.filter_run_excluding :external => true # Only run these tests on platforms that are also chef workstations - config.filter_run_excluding :workstation if solaris? + config.filter_run_excluding :workstation if solaris? or aix? # Tests that randomly fail, but may have value. config.filter_run_excluding :volatile => true diff --git a/spec/support/shared/functional/windows_script.rb b/spec/support/shared/functional/windows_script.rb index f677828167..866caf4b20 100644 --- a/spec/support/shared/functional/windows_script.rb +++ b/spec/support/shared/functional/windows_script.rb @@ -95,7 +95,7 @@ shared_context Chef::Resource::WindowsScript do end let (:architecture) { :x86_64 } - it "should execute a 64-bit guard if the guard's architecture is specified as 64-bit" do + it "should execute a 64-bit guard if the guard's architecture is specified as 64-bit", :windows64_only do resource.only_if resource_guard_command, :architecture => :x86_64 resource.run_action(:run) get_guard_process_architecture.should == 'amd64' diff --git a/spec/unit/chef_fs/file_system_spec.rb b/spec/unit/chef_fs/file_system_spec.rb index 383a2c81ab..50f20a7a1c 100644 --- a/spec/unit/chef_fs/file_system_spec.rb +++ b/spec/unit/chef_fs/file_system_spec.rb @@ -66,18 +66,19 @@ describe Chef::ChefFS::FileSystem do :c => '', } }, - :x => '' + :x => '', + :y => {} }) } context 'list' do it '/**' do - list_should_yield_paths(fs, '/**', '/', '/a', '/x', '/a/aa', '/a/aa/c', '/a/aa/zz', '/a/ab', '/a/ab/c') + list_should_yield_paths(fs, '/**', '/', '/a', '/x', '/y', '/a/aa', '/a/aa/c', '/a/aa/zz', '/a/ab', '/a/ab/c') end it '/' do list_should_yield_paths(fs, '/', '/') end it '/*' do - list_should_yield_paths(fs, '/*', '/', '/a', '/x') + list_should_yield_paths(fs, '/*', '/', '/a', '/x', '/y') end it '/*/*' do list_should_yield_paths(fs, '/*/*', '/a/aa', '/a/ab') @@ -127,8 +128,20 @@ describe Chef::ChefFS::FileSystem do it 'resolves /a/aa/zz' do Chef::ChefFS::FileSystem.resolve_path(fs, '/a/aa/zz').path.should == '/a/aa/zz' end - it 'resolves nonexistent /y/x/w' do - Chef::ChefFS::FileSystem.resolve_path(fs, '/y/x/w').path.should == '/y/x/w' + it 'resolves nonexistent /q/x/w' do + Chef::ChefFS::FileSystem.resolve_path(fs, '/q/x/w').path.should == '/q/x/w' + end + end + + context 'empty?' do + it 'is not empty /' do + Chef::ChefFS::FileSystem.resolve_path(fs, '/').empty?.should be false + end + it 'is empty /y' do + Chef::ChefFS::FileSystem.resolve_path(fs, '/y').empty?.should be true + end + it 'is not a directory and can\'t be tested /x' do + lambda { Chef::ChefFS::FileSystem.resolve_path(fs, '/x').empty? }.should raise_error(NoMethodError) end end end diff --git a/spec/unit/resource/windows_package_spec.rb b/spec/unit/resource/windows_package_spec.rb index 3c45548ece..0034a731b4 100644 --- a/spec/unit/resource/windows_package_spec.rb +++ b/spec/unit/resource/windows_package_spec.rb @@ -34,9 +34,9 @@ describe Chef::Resource::WindowsPackage, "initialize", :windows_only do expect(resource.provider).to eql(Chef::Provider::Package::Windows) end - it "supports setting installer_type" do - resource.installer_type("msi") - expect(resource.installer_type).to eql("msi") + it "supports setting installer_type as a symbol" do + resource.installer_type(:msi) + expect(resource.installer_type).to eql(:msi) end # String, Integer |