Skip to content

Commit

Permalink
feat: add finally lifecycle hook to always run regardless of failure (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
brycekahle committed Oct 22, 2022
1 parent 0c93887 commit 9015ddc
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 5 deletions.
4 changes: 3 additions & 1 deletion docs/content/docs/reference/lifecycle-hooks.md
Expand Up @@ -6,7 +6,7 @@ menu:
weight: 30
---

The life cycle hooks system allows running commands before or after any phase of Test Kitchen (`create`, `converge`, `verify`, or `destroy`). Commands can be run either locally on your workstation (the default) or remotely on the test instance.
The life cycle hooks system allows running commands before, after, or always after any phase of Test Kitchen (`create`, `converge`, `verify`, or `destroy`). Commands can be run either locally on your workstation (the default) or remotely on the test instance.

These hooks are configured under a new `lifecycle:` section in `kitchen.yml`:

Expand All @@ -17,6 +17,8 @@ lifecycle:
- echo after
- local: echo also after
- remote: echo after but in the instance
finally_create:
- echo run regardless of failure in create
```

You can also configure hooks on a single platform or suite:
Expand Down
12 changes: 8 additions & 4 deletions lib/kitchen/lifecycle_hooks.rb
Expand Up @@ -43,9 +43,13 @@ def initialize(config, state_file)
# @param block [Proc] Block of code implementing the lifecycle phase.
# @return [void]
def run_with_hooks(phase, state_file, &block)
run(phase, :pre)
yield
run(phase, :post)
begin
run(phase, :pre)
yield
run(phase, :post)
ensure
run(phase, :finally)
end
end

# @return [Kitchen::StateFile]
Expand All @@ -56,7 +60,7 @@ def run_with_hooks(phase, state_file, &block)
# Execute a specific lifecycle hook.
#
# @param phase [String] Lifecycle phase which is being executed.
# @param hook_timing [Symbol] `:pre` or `:post` to indicate which hook to run.
# @param hook_timing [Symbol] `:pre`, `:post`, or `:finally` to indicate which hook to run.
# @return [void]
def run(phase, hook_timing)
# Yes this has to be a symbol because of how data munger works.
Expand Down
4 changes: 4 additions & 0 deletions spec/kitchen/instance_spec.rb
Expand Up @@ -502,6 +502,7 @@ def platform(name = "platform")
it "calls lifecycle hooks" do
lifecycle_hooks.expects(:run).with(:create, :pre)
lifecycle_hooks.expects(:run).with(:create, :post)
lifecycle_hooks.expects(:run).with(:create, :finally)

instance.create
end
Expand Down Expand Up @@ -559,8 +560,10 @@ def platform(name = "platform")
it "calls lifecycle hooks" do
lifecycle_hooks.expects(:run).with(:create, :pre)
lifecycle_hooks.expects(:run).with(:create, :post)
lifecycle_hooks.expects(:run).with(:create, :finally)
lifecycle_hooks.expects(:run).with(:converge, :pre)
lifecycle_hooks.expects(:run).with(:converge, :post)
lifecycle_hooks.expects(:run).with(:converge, :finally)

instance.converge
end
Expand All @@ -585,6 +588,7 @@ def platform(name = "platform")
it "calls lifecycle hooks" do
lifecycle_hooks.expects(:run).with(:converge, :pre)
lifecycle_hooks.expects(:run).with(:converge, :post)
lifecycle_hooks.expects(:run).with(:converge, :finally)

instance.converge
end
Expand Down
12 changes: 12 additions & 0 deletions spec/kitchen/lifecycle_hooks_spec.rb
Expand Up @@ -108,6 +108,18 @@ def expect_remote_hook_generated_and_run(phase, hook)
run_lifecycle_hooks
end

it "runs finally even if stage fails" do
local_command = "echo foo"
config.update(finally_create: [local_command])
hook = expect_local_hook_generated_and_run(:finally, { local: local_command })
begin
lifecycle_hooks.run_with_hooks(:create, state_file) {
raise Error
}
rescue
end
end

it "runs a local command with a user option" do
hook = { local: "echo foo", user: "bar" }
config.update(post_create: [hook])
Expand Down

0 comments on commit 9015ddc

Please sign in to comment.