spec/unit/command/provision_spec.rb in chef-dk-0.9.0 vs spec/unit/command/provision_spec.rb in chef-dk-0.10.0

- old
+ new

@@ -1,592 +1,592 @@ -# -# Copyright:: Copyright (c) 2015 Chef Software Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'spec_helper' -require 'shared/command_with_ui_object' -require 'chef-dk/command/provision' - -describe ChefDK::Command::Provision do - - it_behaves_like "a command with a UI object" - - let(:command) do - described_class.new - end - - let(:push_service) { instance_double(ChefDK::PolicyfileServices::Push) } - - let(:chef_config_loader) { instance_double("Chef::WorkstationConfigLoader") } - - let(:chef_config) { double("Chef::Config") } - - let(:config_arg) { nil } - - before do - ChefDK::ProvisioningData.reset - - stub_const("Chef::Config", chef_config) - allow(Chef::WorkstationConfigLoader).to receive(:new).with(config_arg).and_return(chef_config_loader) - end - - describe "evaluating CLI options and arguments" do - - let(:ui) { TestHelpers::TestUI.new } - - before do - command.ui = ui - end - - describe "when input is invalid" do - - context "when not enough arguments are given" do - - let(:params) { [] } - - it "prints usage and exits non-zero" do - expect(command.run(params)).to eq(1) - expect(ui.output).to include("You must specify a POLICY_GROUP or disable policyfiles with --no-policy") - end - - end - - context "when --no-policy is combined with policy arguments" do - - let(:params) { %w[ --no-policy some-policy-group ] } - - it "prints usage and exits non-zero" do - expect(command.run(params)).to eq(1) - expect(ui.output).to include("The --no-policy flag cannot be combined with policyfile arguments") - end - - end - - context "when a POLICY_GROUP is given but neither of --sync or --policy-name are given" do - - let(:params) { %w[ some-policy-group ] } - - it "prints usage and exits non-zero" do - expect(command.run(params)).to eq(1) - expect(ui.output).to include("You must pass either --sync or --policy-name to provision machines in policyfile mode") - end - - end - - context "when both --sync and --policy-name are given" do - - let(:params) { %w[ some-policy-group --policy-name foo --sync] } - - it "prints usage and exits non-zero" do - expect(command.run(params)).to eq(1) - expect(ui.output).to include("The --policy-name and --sync arguments cannot be combined") - end - - end - - context "when too many arguments are given" do - - let(:params) { %w[ policygroup extraneous-argument --sync ] } - - it "prints usage and exits non-zero" do - expect(command.run(params)).to eq(1) - expect(ui.output).to include("Too many arguments") - end - - end - end - - describe "when input is valid" do - - let(:context) { ChefDK::ProvisioningData.context } - - shared_examples "common_optional_options" do - - context "with default option values" do - - it "node name is not specified" do - expect(command.node_name).to eq(nil) - expect(context.node_name).to eq(nil) - end - - it "sets the cookbook path to CWD" do - # this is cookbook_path in the chef sense, a directory with cookbooks in it. - expect(command.provisioning_cookbook_path).to eq(Dir.pwd) - end - - it "sets the cookbook name to 'provision'" do - expect(command.provisioning_cookbook_name).to eq('provision') - end - - it "sets the recipe to 'default'" do - expect(command.recipe).to eq("default") - expect(command.chef_runner.run_list).to eq(["recipe[provision::default]"]) - end - - it "sets the default action to converge" do - expect(command.default_action).to eq(:converge) - expect(context.action).to eq(:converge) - end - - end - - context "with -n NODE_NAME" do - - let(:extra_params) { %w[ -n example-node ] } - - it "sets the default requested node name" do - expect(command.node_name).to eq("example-node") - expect(context.node_name).to eq("example-node") - end - - end - - context "with --cookbook COOKBOOK_PATH" do - - let(:extra_params) { %w[ --cookbook ~/mystuff/my-provision-cookbook ] } - - let(:expected_cookbook_path) { File.expand_path("~/mystuff") } - let(:expected_cookbook_name) { "my-provision-cookbook" } - - it "sets the cookbook path" do - # this is cookbook_path in the chef sense, a directory with cookbooks in it. - expect(command.provisioning_cookbook_path).to eq(expected_cookbook_path) - end - - it "sets the cookbook name" do - expect(command.provisioning_cookbook_name).to eq(expected_cookbook_name) - end - - end - - context "with -c CONFIG_FILE" do - - let(:config_arg) { "~/somewhere_else/knife.rb" } - - let(:extra_params) { [ "-c", config_arg ] } - - it "loads config from the specified location" do - # The configurable module uses config[:config_file] - expect(command.config[:config_file]).to eq("~/somewhere_else/knife.rb") - end - - end - - context "with -r MACHINE_RECIPE" do - - let(:extra_params) { %w[ -r ec2cluster ] } - - it "sets the recipe to run as specified" do - expect(command.recipe).to eq("ec2cluster") - expect(command.chef_runner.run_list).to eq(["recipe[provision::ec2cluster]"]) - end - - end - - context "with --target" do - - let(:extra_params) { %w[ -t 192.168.255.123 ] } - - it "sets the target host to the given value" do - expect(context.target).to eq("192.168.255.123") - end - - end - - context "with --opt" do - context "with one user-specified option" do - let(:extra_params) { %w[ --opt color=ebfg ] } - - it "sets the given option name to the given value" do - expect(context.opts.color).to eq("ebfg") - end - end - - context "with an option given as a quoted arg with spaces" do - - let(:extra_params) { [ '--opt', 'color = ebfg' ] } - - it "sets the given option name to the given value" do - expect(context.opts.color).to eq("ebfg") - end - end - - context "with an option with an '=' in it" do - - let(:extra_params) { [ '--opt', 'api_key=abcdef==' ] } - - it "sets the given option name to the given value" do - expect(context.opts.api_key).to eq("abcdef==") - end - end - - context "with an option with a space in it" do - - let(:extra_params) { [ '--opt', 'full_name=Bobo T. Clown' ] } - - it "sets the given option name to the given value" do - expect(context.opts.full_name).to eq("Bobo T. Clown") - end - end - - context "with multiple options given" do - let(:extra_params) { %w[ --opt color=ebfg --opt nope=seppb ] } - - it "sets the given option name to the given value" do - expect(context.opts.color).to eq("ebfg") - expect(context.opts.nope).to eq("seppb") - end - end - end - - context "with -d" do - - let(:extra_params) { %w[ -d ] } - - it "sets the default action to destroy" do - expect(command.default_action).to eq(:destroy) - expect(context.action).to eq(:destroy) - end - - end - - end # shared examples - - context "when --no-policy is given" do - - before do - allow(chef_config_loader).to receive(:load) - allow(command).to receive(:push).and_return(push_service) - - allow(chef_config).to receive(:ssl_verify_mode).and_return(:verify_peer) - - command.apply_params!(params) - command.setup_context - end - - let(:extra_params) { [] } - let(:params) { %w[ --no-policy ] + extra_params } - - it "disables policyfile integration" do - expect(command.enable_policyfile?).to be(false) - end - - it "generates chef config with no policyfile options" do - expected_config = <<-CONFIG -# SSL Settings: -ssl_verify_mode :verify_peer - -CONFIG - expect(context.chef_config).to eq(expected_config) - end - - include_examples "common_optional_options" - - end # when --no-policy is given - - context "when --sync POLICYFILE argument is given" do - - let(:policy_data) { { "name" => "myapp" } } - - before do - allow(chef_config_loader).to receive(:load) - - allow(ChefDK::PolicyfileServices::Push).to receive(:new). - with(policyfile: given_policyfile_path, ui: ui, policy_group: given_policy_group, config: chef_config, root_dir: Dir.pwd). - and_return(push_service) - - allow(push_service).to receive(:policy_data).and_return(policy_data) - - command.apply_params!(params) - command.setup_context - end - - context "with explicit policyfile relative path" do - - let(:given_policyfile_path) { "policies/OtherPolicy.rb" } - - let(:given_policy_group) { "some-policy-group" } - - let(:params) { [ given_policy_group, '--sync', given_policyfile_path ] } - - it "sets policy group" do - expect(command.policy_group).to eq(given_policy_group) - expect(context.policy_group).to eq(given_policy_group) - end - - it "sets policy name" do - expect(command.policy_name).to eq("myapp") - expect(context.policy_name).to eq("myapp") - end - - end - - context "with implicit policyfile relative path" do - - let(:given_policyfile_path) { nil } - - let(:given_policy_group) { "some-policy-group" } - - let(:extra_params) { [] } - - let(:params) { [ given_policy_group, '--sync' ] + extra_params } - - before do - allow(chef_config).to receive(:ssl_verify_mode).and_return(:verify_peer) - end - - it "sets policy group" do - expect(command.policy_group).to eq(given_policy_group) - expect(context.policy_group).to eq(given_policy_group) - end - - it "sets policy name" do - expect(command.policy_name).to eq("myapp") - expect(context.policy_name).to eq("myapp") - end - - it "generates chef config with policyfile options" do - expected_config = <<-CONFIG -# SSL Settings: -ssl_verify_mode :verify_peer - -# Policyfile Settings: -use_policyfile true -policy_document_native_api true - -policy_group "some-policy-group" -policy_name "myapp" - -CONFIG - expect(context.chef_config).to eq(expected_config) - end - - - include_examples "common_optional_options" - - end - - end # when --sync POLICYFILE argument is given - - context "when a --policy-name is given" do - - let(:given_policy_group) { "some-policy-group" } - - let(:extra_params) { [] } - - let(:params) { [ given_policy_group, '--policy-name', "myapp" ] + extra_params } - - - before do - command.apply_params!(params) - command.setup_context - - allow(chef_config).to receive(:ssl_verify_mode).and_return(:verify_peer) - end - - it "sets policy group" do - expect(command.policy_group).to eq(given_policy_group) - expect(context.policy_group).to eq(given_policy_group) - end - - it "sets policy name" do - expect(command.policy_name).to eq("myapp") - expect(context.policy_name).to eq("myapp") - end - - it "generates chef config with policyfile options" do - expected_config = <<-CONFIG -# SSL Settings: -ssl_verify_mode :verify_peer - -# Policyfile Settings: -use_policyfile true -policy_document_native_api true - -policy_group "some-policy-group" -policy_name "myapp" - -CONFIG - expect(context.chef_config).to eq(expected_config) - end - - include_examples "common_optional_options" - - end - end - - end - - describe "running the provision cookbook" do - - let(:ui) { TestHelpers::TestUI.new } - - before do - allow(chef_config_loader).to receive(:load) - allow(command).to receive(:push).and_return(push_service) - command.ui = ui - end - - let(:provision_cookbook_path) { File.expand_path("provision", Dir.pwd) } - let(:provision_recipe_path) { File.join(provision_cookbook_path, "recipes", "default.rb") } - - let(:chef_runner) { instance_double("ChefDK::ChefRunner") } - - let(:params) { %w[ policygroup --sync ] } - - context "when the provision cookbook doesn't exist" do - - before do - allow(File).to receive(:exist?).with(provision_cookbook_path).and_return(false) - end - - it "prints an error and exits non-zero" do - expect(command.run(params)).to eq(1) - expect(ui.output).to include("Provisioning cookbook not found at path #{provision_cookbook_path}") - end - - end - - context "when the provision cookbook doesn't have the requested recipe" do - - before do - allow(File).to receive(:exist?).with(provision_cookbook_path).and_return(true) - allow(File).to receive(:exist?).with(provision_recipe_path).and_return(false) - end - - it "prints an error and exits non-zero" do - expect(command.run(params)).to eq(1) - expect(ui.output).to include("Provisioning recipe not found at path #{provision_recipe_path}") - end - - end - - context "when the policyfile upload fails" do - - let(:backtrace) { caller[0...3] } - - let(:cause) do - e = StandardError.new("some operation failed") - e.set_backtrace(backtrace) - e - end - - let(:exception) do - ChefDK::PolicyfilePushError.new("push failed", cause) - end - - before do - allow(File).to receive(:exist?).with(provision_cookbook_path).and_return(true) - allow(File).to receive(:exist?).with(provision_recipe_path).and_return(true) - - expect(push_service).to receive(:run).and_raise(exception) - end - - it "prints an error and exits non-zero" do - expected_output=<<-E -Error: push failed -Reason: (StandardError) some operation failed - -E - expect(command.run(params)).to eq(1) - expect(ui.output).to include(expected_output) - end - - end - - context "when the chef run fails" do - - let(:base_exception) { StandardError.new("Something went wrong") } - let(:exception) { ChefDK::ChefConvergeError.new("Chef failed to converge: #{base_exception}", base_exception) } - - let(:policy_data) { { "name" => "myapp" } } - - before do - allow(File).to receive(:exist?).with(provision_cookbook_path).and_return(true) - allow(File).to receive(:exist?).with(provision_recipe_path).and_return(true) - - allow(push_service).to receive(:policy_data).and_return(policy_data) - - expect(push_service).to receive(:run) - - allow(command).to receive(:chef_runner).and_return(chef_runner) - allow(chef_runner).to receive(:cookbook_path).and_return(Dir.pwd) - expect(chef_runner).to receive(:converge).and_raise(exception) - end - - it "prints an error and exits non-zero" do - expect(command.run(params)).to eq(1) - expect(ui.output).to include("Error: Chef failed to converge") - expect(ui.output).to include("Reason: (StandardError) Something went wrong") - end - - end - - context "when the chef run is successful" do - - before do - allow(File).to receive(:exist?).with(provision_cookbook_path).and_return(true) - allow(File).to receive(:exist?).with(provision_recipe_path).and_return(true) - allow(command).to receive(:chef_runner).and_return(chef_runner) - allow(chef_runner).to receive(:cookbook_path).and_return(Dir.pwd) - - expect(chef_runner).to receive(:converge) - end - - context "when using --no-policy" do - - let(:params) { %w[ --no-policy ] } - - it "exits 0" do - return_value = command.run(params) - expect(ui.output).to eq("") - expect(return_value).to eq(0) - end - - end - - context "with --policy-name" do - - let(:params) { %w[ policygroup --policy-name otherapp ] } - - it "exits 0" do - return_value = command.run(params) - expect(ui.output).to eq("") - expect(return_value).to eq(0) - end - end - - context "with --sync" do - - let(:policy_data) { { "name" => "myapp" } } - - before do - allow(push_service).to receive(:policy_data).and_return(policy_data) - expect(push_service).to receive(:run) - end - - it "exits 0" do - return_value = command.run(params) - expect(ui.output).to eq("") - expect(return_value).to eq(0) - end - - end - - end - - end -end - +# +# Copyright:: Copyright (c) 2015 Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' +require 'shared/command_with_ui_object' +require 'chef-dk/command/provision' + +describe ChefDK::Command::Provision do + + it_behaves_like "a command with a UI object" + + let(:command) do + described_class.new + end + + let(:push_service) { instance_double(ChefDK::PolicyfileServices::Push) } + + let(:chef_config_loader) { instance_double("Chef::WorkstationConfigLoader") } + + let(:chef_config) { double("Chef::Config") } + + let(:config_arg) { nil } + + before do + ChefDK::ProvisioningData.reset + + stub_const("Chef::Config", chef_config) + allow(Chef::WorkstationConfigLoader).to receive(:new).with(config_arg).and_return(chef_config_loader) + end + + describe "evaluating CLI options and arguments" do + + let(:ui) { TestHelpers::TestUI.new } + + before do + command.ui = ui + end + + describe "when input is invalid" do + + context "when not enough arguments are given" do + + let(:params) { [] } + + it "prints usage and exits non-zero" do + expect(command.run(params)).to eq(1) + expect(ui.output).to include("You must specify a POLICY_GROUP or disable policyfiles with --no-policy") + end + + end + + context "when --no-policy is combined with policy arguments" do + + let(:params) { %w[ --no-policy some-policy-group ] } + + it "prints usage and exits non-zero" do + expect(command.run(params)).to eq(1) + expect(ui.output).to include("The --no-policy flag cannot be combined with policyfile arguments") + end + + end + + context "when a POLICY_GROUP is given but neither of --sync or --policy-name are given" do + + let(:params) { %w[ some-policy-group ] } + + it "prints usage and exits non-zero" do + expect(command.run(params)).to eq(1) + expect(ui.output).to include("You must pass either --sync or --policy-name to provision machines in policyfile mode") + end + + end + + context "when both --sync and --policy-name are given" do + + let(:params) { %w[ some-policy-group --policy-name foo --sync] } + + it "prints usage and exits non-zero" do + expect(command.run(params)).to eq(1) + expect(ui.output).to include("The --policy-name and --sync arguments cannot be combined") + end + + end + + context "when too many arguments are given" do + + let(:params) { %w[ policygroup extraneous-argument --sync ] } + + it "prints usage and exits non-zero" do + expect(command.run(params)).to eq(1) + expect(ui.output).to include("Too many arguments") + end + + end + end + + describe "when input is valid" do + + let(:context) { ChefDK::ProvisioningData.context } + + shared_examples "common_optional_options" do + + context "with default option values" do + + it "node name is not specified" do + expect(command.node_name).to eq(nil) + expect(context.node_name).to eq(nil) + end + + it "sets the cookbook path to CWD" do + # this is cookbook_path in the chef sense, a directory with cookbooks in it. + expect(command.provisioning_cookbook_path).to eq(Dir.pwd) + end + + it "sets the cookbook name to 'provision'" do + expect(command.provisioning_cookbook_name).to eq('provision') + end + + it "sets the recipe to 'default'" do + expect(command.recipe).to eq("default") + expect(command.chef_runner.run_list).to eq(["recipe[provision::default]"]) + end + + it "sets the default action to converge" do + expect(command.default_action).to eq(:converge) + expect(context.action).to eq(:converge) + end + + end + + context "with -n NODE_NAME" do + + let(:extra_params) { %w[ -n example-node ] } + + it "sets the default requested node name" do + expect(command.node_name).to eq("example-node") + expect(context.node_name).to eq("example-node") + end + + end + + context "with --cookbook COOKBOOK_PATH" do + + let(:extra_params) { %w[ --cookbook ~/mystuff/my-provision-cookbook ] } + + let(:expected_cookbook_path) { File.expand_path("~/mystuff") } + let(:expected_cookbook_name) { "my-provision-cookbook" } + + it "sets the cookbook path" do + # this is cookbook_path in the chef sense, a directory with cookbooks in it. + expect(command.provisioning_cookbook_path).to eq(expected_cookbook_path) + end + + it "sets the cookbook name" do + expect(command.provisioning_cookbook_name).to eq(expected_cookbook_name) + end + + end + + context "with -c CONFIG_FILE" do + + let(:config_arg) { "~/somewhere_else/knife.rb" } + + let(:extra_params) { [ "-c", config_arg ] } + + it "loads config from the specified location" do + # The configurable module uses config[:config_file] + expect(command.config[:config_file]).to eq("~/somewhere_else/knife.rb") + end + + end + + context "with -r MACHINE_RECIPE" do + + let(:extra_params) { %w[ -r ec2cluster ] } + + it "sets the recipe to run as specified" do + expect(command.recipe).to eq("ec2cluster") + expect(command.chef_runner.run_list).to eq(["recipe[provision::ec2cluster]"]) + end + + end + + context "with --target" do + + let(:extra_params) { %w[ -t 192.168.255.123 ] } + + it "sets the target host to the given value" do + expect(context.target).to eq("192.168.255.123") + end + + end + + context "with --opt" do + context "with one user-specified option" do + let(:extra_params) { %w[ --opt color=ebfg ] } + + it "sets the given option name to the given value" do + expect(context.opts.color).to eq("ebfg") + end + end + + context "with an option given as a quoted arg with spaces" do + + let(:extra_params) { [ '--opt', 'color = ebfg' ] } + + it "sets the given option name to the given value" do + expect(context.opts.color).to eq("ebfg") + end + end + + context "with an option with an '=' in it" do + + let(:extra_params) { [ '--opt', 'api_key=abcdef==' ] } + + it "sets the given option name to the given value" do + expect(context.opts.api_key).to eq("abcdef==") + end + end + + context "with an option with a space in it" do + + let(:extra_params) { [ '--opt', 'full_name=Bobo T. Clown' ] } + + it "sets the given option name to the given value" do + expect(context.opts.full_name).to eq("Bobo T. Clown") + end + end + + context "with multiple options given" do + let(:extra_params) { %w[ --opt color=ebfg --opt nope=seppb ] } + + it "sets the given option name to the given value" do + expect(context.opts.color).to eq("ebfg") + expect(context.opts.nope).to eq("seppb") + end + end + end + + context "with -d" do + + let(:extra_params) { %w[ -d ] } + + it "sets the default action to destroy" do + expect(command.default_action).to eq(:destroy) + expect(context.action).to eq(:destroy) + end + + end + + end # shared examples + + context "when --no-policy is given" do + + before do + allow(chef_config_loader).to receive(:load) + allow(command).to receive(:push).and_return(push_service) + + allow(chef_config).to receive(:ssl_verify_mode).and_return(:verify_peer) + + command.apply_params!(params) + command.setup_context + end + + let(:extra_params) { [] } + let(:params) { %w[ --no-policy ] + extra_params } + + it "disables policyfile integration" do + expect(command.enable_policyfile?).to be(false) + end + + it "generates chef config with no policyfile options" do + expected_config = <<-CONFIG +# SSL Settings: +ssl_verify_mode :verify_peer + +CONFIG + expect(context.chef_config).to eq(expected_config) + end + + include_examples "common_optional_options" + + end # when --no-policy is given + + context "when --sync POLICYFILE argument is given" do + + let(:policy_data) { { "name" => "myapp" } } + + before do + allow(chef_config_loader).to receive(:load) + + allow(ChefDK::PolicyfileServices::Push).to receive(:new). + with(policyfile: given_policyfile_path, ui: ui, policy_group: given_policy_group, config: chef_config, root_dir: Dir.pwd). + and_return(push_service) + + allow(push_service).to receive(:policy_data).and_return(policy_data) + + command.apply_params!(params) + command.setup_context + end + + context "with explicit policyfile relative path" do + + let(:given_policyfile_path) { "policies/OtherPolicy.rb" } + + let(:given_policy_group) { "some-policy-group" } + + let(:params) { [ given_policy_group, '--sync', given_policyfile_path ] } + + it "sets policy group" do + expect(command.policy_group).to eq(given_policy_group) + expect(context.policy_group).to eq(given_policy_group) + end + + it "sets policy name" do + expect(command.policy_name).to eq("myapp") + expect(context.policy_name).to eq("myapp") + end + + end + + context "with implicit policyfile relative path" do + + let(:given_policyfile_path) { nil } + + let(:given_policy_group) { "some-policy-group" } + + let(:extra_params) { [] } + + let(:params) { [ given_policy_group, '--sync' ] + extra_params } + + before do + allow(chef_config).to receive(:ssl_verify_mode).and_return(:verify_peer) + end + + it "sets policy group" do + expect(command.policy_group).to eq(given_policy_group) + expect(context.policy_group).to eq(given_policy_group) + end + + it "sets policy name" do + expect(command.policy_name).to eq("myapp") + expect(context.policy_name).to eq("myapp") + end + + it "generates chef config with policyfile options" do + expected_config = <<-CONFIG +# SSL Settings: +ssl_verify_mode :verify_peer + +# Policyfile Settings: +use_policyfile true +policy_document_native_api true + +policy_group "some-policy-group" +policy_name "myapp" + +CONFIG + expect(context.chef_config).to eq(expected_config) + end + + + include_examples "common_optional_options" + + end + + end # when --sync POLICYFILE argument is given + + context "when a --policy-name is given" do + + let(:given_policy_group) { "some-policy-group" } + + let(:extra_params) { [] } + + let(:params) { [ given_policy_group, '--policy-name', "myapp" ] + extra_params } + + + before do + command.apply_params!(params) + command.setup_context + + allow(chef_config).to receive(:ssl_verify_mode).and_return(:verify_peer) + end + + it "sets policy group" do + expect(command.policy_group).to eq(given_policy_group) + expect(context.policy_group).to eq(given_policy_group) + end + + it "sets policy name" do + expect(command.policy_name).to eq("myapp") + expect(context.policy_name).to eq("myapp") + end + + it "generates chef config with policyfile options" do + expected_config = <<-CONFIG +# SSL Settings: +ssl_verify_mode :verify_peer + +# Policyfile Settings: +use_policyfile true +policy_document_native_api true + +policy_group "some-policy-group" +policy_name "myapp" + +CONFIG + expect(context.chef_config).to eq(expected_config) + end + + include_examples "common_optional_options" + + end + end + + end + + describe "running the provision cookbook" do + + let(:ui) { TestHelpers::TestUI.new } + + before do + allow(chef_config_loader).to receive(:load) + allow(command).to receive(:push).and_return(push_service) + command.ui = ui + end + + let(:provision_cookbook_path) { File.expand_path("provision", Dir.pwd) } + let(:provision_recipe_path) { File.join(provision_cookbook_path, "recipes", "default.rb") } + + let(:chef_runner) { instance_double("ChefDK::ChefRunner") } + + let(:params) { %w[ policygroup --sync ] } + + context "when the provision cookbook doesn't exist" do + + before do + allow(File).to receive(:exist?).with(provision_cookbook_path).and_return(false) + end + + it "prints an error and exits non-zero" do + expect(command.run(params)).to eq(1) + expect(ui.output).to include("Provisioning cookbook not found at path #{provision_cookbook_path}") + end + + end + + context "when the provision cookbook doesn't have the requested recipe" do + + before do + allow(File).to receive(:exist?).with(provision_cookbook_path).and_return(true) + allow(File).to receive(:exist?).with(provision_recipe_path).and_return(false) + end + + it "prints an error and exits non-zero" do + expect(command.run(params)).to eq(1) + expect(ui.output).to include("Provisioning recipe not found at path #{provision_recipe_path}") + end + + end + + context "when the policyfile upload fails" do + + let(:backtrace) { caller[0...3] } + + let(:cause) do + e = StandardError.new("some operation failed") + e.set_backtrace(backtrace) + e + end + + let(:exception) do + ChefDK::PolicyfilePushError.new("push failed", cause) + end + + before do + allow(File).to receive(:exist?).with(provision_cookbook_path).and_return(true) + allow(File).to receive(:exist?).with(provision_recipe_path).and_return(true) + + expect(push_service).to receive(:run).and_raise(exception) + end + + it "prints an error and exits non-zero" do + expected_output=<<-E +Error: push failed +Reason: (StandardError) some operation failed + +E + expect(command.run(params)).to eq(1) + expect(ui.output).to include(expected_output) + end + + end + + context "when the chef run fails" do + + let(:base_exception) { StandardError.new("Something went wrong") } + let(:exception) { ChefDK::ChefConvergeError.new("Chef failed to converge: #{base_exception}", base_exception) } + + let(:policy_data) { { "name" => "myapp" } } + + before do + allow(File).to receive(:exist?).with(provision_cookbook_path).and_return(true) + allow(File).to receive(:exist?).with(provision_recipe_path).and_return(true) + + allow(push_service).to receive(:policy_data).and_return(policy_data) + + expect(push_service).to receive(:run) + + allow(command).to receive(:chef_runner).and_return(chef_runner) + allow(chef_runner).to receive(:cookbook_path).and_return(Dir.pwd) + expect(chef_runner).to receive(:converge).and_raise(exception) + end + + it "prints an error and exits non-zero" do + expect(command.run(params)).to eq(1) + expect(ui.output).to include("Error: Chef failed to converge") + expect(ui.output).to include("Reason: (StandardError) Something went wrong") + end + + end + + context "when the chef run is successful" do + + before do + allow(File).to receive(:exist?).with(provision_cookbook_path).and_return(true) + allow(File).to receive(:exist?).with(provision_recipe_path).and_return(true) + allow(command).to receive(:chef_runner).and_return(chef_runner) + allow(chef_runner).to receive(:cookbook_path).and_return(Dir.pwd) + + expect(chef_runner).to receive(:converge) + end + + context "when using --no-policy" do + + let(:params) { %w[ --no-policy ] } + + it "exits 0" do + return_value = command.run(params) + expect(ui.output).to eq("") + expect(return_value).to eq(0) + end + + end + + context "with --policy-name" do + + let(:params) { %w[ policygroup --policy-name otherapp ] } + + it "exits 0" do + return_value = command.run(params) + expect(ui.output).to eq("") + expect(return_value).to eq(0) + end + end + + context "with --sync" do + + let(:policy_data) { { "name" => "myapp" } } + + before do + allow(push_service).to receive(:policy_data).and_return(policy_data) + expect(push_service).to receive(:run) + end + + it "exits 0" do + return_value = command.run(params) + expect(ui.output).to eq("") + expect(return_value).to eq(0) + end + + end + + end + + end +end +