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
+