require 'support/shared/integration/integration_helper' require 'chef/mixin/shell_out' require 'tiny_server' require 'tmpdir' describe "chef-client" do def recipes_filename File.join(CHEF_SPEC_DATA, 'recipes.tgz') end def start_tiny_server(server_opts={}) @server = TinyServer::Manager.new(server_opts) @server.start @api = TinyServer::API.instance @api.clear # # trivial endpoints # # just a normal file # (expected_content should be uncompressed) @api.get("/recipes.tgz", 200) { File.open(recipes_filename, "rb") do |f| f.read end } end def stop_tiny_server @server.stop @server = @api = nil end include IntegrationSupport include Chef::Mixin::ShellOut let(:chef_dir) { File.join(File.dirname(__FILE__), "..", "..", "..", "bin") } # Invoke `chef-client` as `ruby PATH/TO/chef-client`. This ensures the # following constraints are satisfied: # * Windows: windows can only run batch scripts as bare executables. Rubygems # creates batch wrappers for installed gems, but we don't have batch wrappers # in the source tree. # * Other `chef-client` in PATH: A common case is running the tests on a # machine that has omnibus chef installed. In that case we need to ensure # we're running `chef-client` from the source tree and not the external one. # cf. CHEF-4914 let(:chef_client) { "ruby '#{chef_dir}/chef-client' --minimal-ohai" } let(:critical_env_vars) { %w(PATH RUBYOPT BUNDLE_GEMFILE GEM_PATH).map {|o| "#{o}=#{ENV[o]}"} .join(' ') } when_the_repository "has a cookbook with a no-op recipe" do before { file 'cookbooks/x/recipes/default.rb', '' } it "should complete with success" do file 'config/client.rb', < chef_dir) end it "should complete successfully with no other environment variables", :skip => (Chef::Platform.windows?) do file 'config/client.rb', < chef_dir) result.error! rescue Chef::Log.info "Bare invocation will have the following load-path." Chef::Log.info shell_out!("env -i #{critical_env_vars} ruby -e 'puts $:'").stdout raise end end it "should complete successfully with --no-listen" do file 'config/client.rb', < chef_dir) result.error! end it "should be able to node.save with bad utf8 characters in the node data" do file "cookbooks/x/attributes/default.rb", 'default["badutf8"] = "Elan Ruusam\xE4e"' result = shell_out("#{chef_client} -z -r 'x::default' --disable-config", :cwd => path_to('')) result.error! end context 'and no config file' do it 'should complete with success when cwd is just above cookbooks and paths are not specified' do result = shell_out("#{chef_client} -z -o 'x::default' --disable-config", :cwd => path_to('')) result.error! end it 'should complete with success when cwd is below cookbooks and paths are not specified' do result = shell_out("#{chef_client} -z -o 'x::default' --disable-config", :cwd => path_to('cookbooks/x')) result.error! end it 'should fail when cwd is below high above and paths are not specified' do result = shell_out("#{chef_client} -z -o 'x::default' --disable-config", :cwd => File.expand_path('..', path_to(''))) expect(result.exitstatus).to eq(1) end end context 'and a config file under .chef/knife.rb' do before { file '.chef/knife.rb', 'xxx.xxx' } it 'should load .chef/knife.rb when -z is specified' do result = shell_out("#{chef_client} -z -o 'x::default'", :cwd => path_to('')) # FATAL: Configuration error NoMethodError: undefined method `xxx' for nil:NilClass expect(result.stdout).to include("xxx") end end it "should complete with success" do file 'config/client.rb', < chef_dir) result.error! end context 'and a private key' do before do file 'mykey.pem', < chef_dir) result.error! end it "should run recipes specified directly on the command line" do file 'config/client.rb', < chef_dir) result.error! expect(IO.read(path_to('tempfile.txt'))).to eq('1') expect(IO.read(path_to('tempfile2.txt'))).to eq('2') end it "should run recipes specified as relative paths directly on the command line" do file 'config/client.rb', < path_to('')) result.error! expect(IO.read(path_to('tempfile.txt'))).to eq('1') end it "should run recipes specified directly on the command line AFTER recipes in the run list" do file 'config/client.rb', < path_to('')) result.error! expect(IO.read(path_to('tempfile.txt'))).to eq('1') end end it "should complete with success when passed the -z flag" do file 'config/client.rb', < chef_dir) result.error! end it "should complete with success when passed the --local-mode flag" do file 'config/client.rb', < chef_dir) result.error! end it "should not print SSL warnings when running in local-mode" do file 'config/client.rb', < chef_dir) expect(result.stdout).not_to include("SSL validation of HTTPS requests is disabled.") result.error! end it "should complete with success when passed -z and --chef-zero-port" do file 'config/client.rb', < chef_dir) result.error! end it "should complete with success when setting the run list with -r" do file 'config/client.rb', < chef_dir) expect(result.stdout).not_to include("Overridden Run List") expect(result.stdout).to include("Run List is [recipe[x::default]]") #puts result.stdout result.error! end end when_the_repository "has a cookbook that generates deprecation warnings" do before do file 'cookbooks/x/recipes/default.rb', <<-EOM class ::MyResource < Chef::Resource use_automatic_resource_name property :x, default: [] property :y, default: {} end my_resource 'blah' do 1.upto(10) do x nil end x nil end EOM end def match_indices(regex, str) result = [] pos = 0 while match = regex.match(str, pos) result << match.begin(0) pos = match.end(0) + 1 end result end it "should output each deprecation warning only once, at the end of the run" do file 'config/client.rb', < chef_dir) expect(result.error?).to be_falsey # Search to the end of the client run in the output run_complete = result.stdout.index("Running handlers complete") expect(run_complete).to be >= 0 # Make sure there is exactly one result for each, and that it occurs *after* the complete message. expect(match_indices(/nil currently does not overwrite the value of/, result.stdout)).to match([ be > run_complete ]) end end when_the_repository "has a cookbook with only an audit recipe" do before do file 'config/client.rb', < chef_dir) expect(result.error?).to be_falsey expect(result.stdout).to include("Successfully executed all `control_group` blocks and contained examples") end it "should exit with a non-zero code when there is an audit failure" do file 'cookbooks/audit_test/recipes/fail.rb', <<-RECIPE control_group "control group without top level control" do it "should fail" do expect(2 - 2).to eq(1) end end RECIPE result = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'audit_test::fail'", :cwd => chef_dir) expect(result.error?).to be_truthy expect(result.stdout).to include("Failure/Error: expect(2 - 2).to eq(1)") end end # Fails on appveyor, but works locally on windows and on windows hosts in Ci. context "when using recipe-url", :skip_appveyor do before(:all) do start_tiny_server end after(:all) do stop_tiny_server end let(:tmp_dir) { Dir.mktmpdir("recipe-url") } it "should complete with success when passed -z and --recipe-url" do file 'config/client.rb', < tmp_dir) result.error! end it 'should fail when passed --recipe-url and not passed -z' do result = shell_out("#{chef_client} --recipe-url=http://localhost:9000/recipes.tgz", :cwd => tmp_dir) expect(result.exitstatus).not_to eq(0) end end end