# -*- encoding: utf-8 -*- # # Author:: Noah Kantrowitz # # Copyright (C) 2018, Noah Kantrowitz # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "kitchen/errors" require "kitchen/lifecycle_hooks" describe Kitchen::LifecycleHooks do let(:suite) { mock("suite").tap { |i| i.stubs(name: "default") } } let(:platform) { mock("platform").tap { |i| i.stubs(name: "toaster-1.0") } } let(:state_file) { mock("state_file").tap { |s| s.stubs(read: { hostname: "localhost" }) } } let(:connection) { mock("connection") } let(:transport) { mock("transport").tap { |t| t.stubs(:connection).with({ hostname: "localhost" }).returns(connection) } } let(:last_action) { :create } let(:instance) { mock("instance").tap { |i| i.stubs(name: "default-toaster-10", transport: transport, last_action: last_action, suite: suite, platform: platform) } } let(:config) { { kitchen_root: "/kitchen" } } let(:lifecycle_hooks) { Kitchen::LifecycleHooks.new(config).tap { |lh| lh.finalize_config!(instance) } } def run_lifecycle_hooks lifecycle_hooks.run_with_hooks(:create, state_file) {} end # Pull this out because it's used in a bunch of tests. STANDARD_LOCAL_OPTIONS = { cwd: "/kitchen", environment: { "KITCHEN_INSTANCE_NAME" => "default-toaster-10", "KITCHEN_SUITE_NAME" => "default", "KITCHEN_PLATFORM_NAME" => "toaster-1.0", "KITCHEN_INSTANCE_HOSTNAME" => "localhost", } } it "runs a single local command" do config.update(post_create: ["echo foo"]) lifecycle_hooks.expects(:run_command).with("echo foo", STANDARD_LOCAL_OPTIONS) run_lifecycle_hooks end it "runs multiple local commands" do config.update(post_create: ["echo foo", { local: "echo bar" }]) lifecycle_hooks.expects(:run_command).with("echo foo", STANDARD_LOCAL_OPTIONS) lifecycle_hooks.expects(:run_command).with("echo bar", STANDARD_LOCAL_OPTIONS) run_lifecycle_hooks end it "runs multiple local hooks" do config.update(pre_create: ["echo foo"], post_create: ["echo bar"]) lifecycle_hooks.expects(:run_command).with("echo foo", STANDARD_LOCAL_OPTIONS) lifecycle_hooks.expects(:run_command).with("echo bar", STANDARD_LOCAL_OPTIONS) run_lifecycle_hooks end it "runs a local command with a user option" do config.update(post_create: [{ local: "echo foo", user: "bar" }]) lifecycle_hooks.expects(:run_command).with("echo foo", { cwd: "/kitchen", user: "bar", environment: { "KITCHEN_INSTANCE_NAME" => "default-toaster-10", "KITCHEN_SUITE_NAME" => "default", "KITCHEN_PLATFORM_NAME" => "toaster-1.0", "KITCHEN_INSTANCE_HOSTNAME" => "localhost", } }) run_lifecycle_hooks end it "runs a local command with environment options" do config.update(post_create: [{ local: "echo foo", environment: { FOO: "one", BAR: "two" } }]) lifecycle_hooks.expects(:run_command).with("echo foo", { cwd: "/kitchen", environment: { "FOO" => "one", "BAR" => "two", "KITCHEN_INSTANCE_NAME" => "default-toaster-10", "KITCHEN_SUITE_NAME" => "default", "KITCHEN_PLATFORM_NAME" => "toaster-1.0", "KITCHEN_INSTANCE_HOSTNAME" => "localhost", } }) run_lifecycle_hooks end it "runs a local command with a relative cwd option" do config.update(post_create: [{ local: "echo foo", cwd: "test" }]) lifecycle_hooks.expects(:run_command).with("echo foo", { cwd: os_safe_root_path("/kitchen/test"), environment: { "KITCHEN_INSTANCE_NAME" => "default-toaster-10", "KITCHEN_SUITE_NAME" => "default", "KITCHEN_PLATFORM_NAME" => "toaster-1.0", "KITCHEN_INSTANCE_HOSTNAME" => "localhost", } }) run_lifecycle_hooks end it "runs a local command with an absolute cwd option" do config.update(post_create: [{ local: "echo foo", cwd: "/test" }]) lifecycle_hooks.expects(:run_command).with("echo foo", { cwd: os_safe_root_path("/test"), environment: { "KITCHEN_INSTANCE_NAME" => "default-toaster-10", "KITCHEN_SUITE_NAME" => "default", "KITCHEN_PLATFORM_NAME" => "toaster-1.0", "KITCHEN_INSTANCE_HOSTNAME" => "localhost", } }) run_lifecycle_hooks end it "runs a single remote command" do config.update(post_create: [{ remote: "echo foo" }]) lifecycle_hooks.expects(:run_command).never connection.expects(:execute).with("echo foo") run_lifecycle_hooks end it "rejects unknown hook targets" do config.update(post_create: [{ banana: "echo foo" }]) lifecycle_hooks.expects(:run_command).never proc { run_lifecycle_hooks }.must_raise Kitchen::UserError end it "runs mixed local and remote commands" do config.update(post_create: ["echo foo", { local: "echo bar" }, { remote: "echo baz" }]) lifecycle_hooks.expects(:run_command).with("echo foo", STANDARD_LOCAL_OPTIONS) lifecycle_hooks.expects(:run_command).with("echo bar", STANDARD_LOCAL_OPTIONS) connection.expects(:execute).with("echo baz") run_lifecycle_hooks end describe "with no last_action" do let(:last_action) { nil } it "runs local commands" do config.update(post_create: [{ local: "echo foo" }]) lifecycle_hooks.expects(:run_command).with("echo foo", STANDARD_LOCAL_OPTIONS) run_lifecycle_hooks end it "fails on remote commands" do config.update(post_create: [{ remote: "echo foo" }]) lifecycle_hooks.expects(:run_command).never proc { run_lifecycle_hooks }.must_raise Kitchen::UserError end it "ignores skippable remote commands" do config.update(post_create: [{ remote: "echo foo", skippable: true }]) lifecycle_hooks.expects(:run_command).never run_lifecycle_hooks end end end