foreman_hooks 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +31 -6
- data/TODO +0 -1
- data/foreman_hooks.gemspec +2 -2
- data/lib/foreman_hooks/engine.rb +11 -0
- data/lib/foreman_hooks/hooks_observer.rb +6 -70
- data/lib/foreman_hooks/orchestration_hook.rb +63 -0
- data/lib/foreman_hooks/util.rb +76 -0
- metadata +4 -2
data/README.md
CHANGED
@@ -33,9 +33,9 @@ Each file within the directory is executed in alphabetical order.
|
|
33
33
|
|
34
34
|
Examples:
|
35
35
|
|
36
|
+
~foreman/config/hooks/host/create/50_register_system.sh
|
37
|
+
~foreman/config/hooks/host/destroy/15_cleanup_database.sh
|
36
38
|
~foreman/config/hooks/smart_proxy/after_create/01_email_operations.sh
|
37
|
-
~foreman/config/hooks/host/before_provision/50_do_something.sh
|
38
|
-
~foreman/config/hooks/host/managed/after_destroy/15_cleanup_database.sh
|
39
39
|
|
40
40
|
Note that in Foreman 1.1, hosts are just named `Host` so hooks go in a `host/`
|
41
41
|
directory, while in Foreman 1.2 they're `Host::Base` and `Host::Managed`, so
|
@@ -50,7 +50,20 @@ Every object (or model in Rails terms) in Foreman can have hooks. Check
|
|
50
50
|
* `host/discovered` (Foreman 1.2)
|
51
51
|
* `report`
|
52
52
|
|
53
|
-
##
|
53
|
+
## Orchestration events
|
54
|
+
|
55
|
+
Foreman supports orchestration tasks for hosts and NICs (each network
|
56
|
+
interface) which happen when the object is created, updated and destroyed.
|
57
|
+
These tasks are shown to the user in the UI and if they fail, will
|
58
|
+
automatically trigger a rollback of the action.
|
59
|
+
|
60
|
+
To add hooks to these, use these event names:
|
61
|
+
|
62
|
+
* `create`
|
63
|
+
* `update`
|
64
|
+
* `destroy`
|
65
|
+
|
66
|
+
## Rails events
|
54
67
|
|
55
68
|
These are the most interesting events that Rails provides and this plugin
|
56
69
|
exposes:
|
@@ -71,9 +84,21 @@ The host object has two special callbacks in Foreman 1.1 that you can use:
|
|
71
84
|
## Execution of hooks
|
72
85
|
|
73
86
|
Hooks are executed in the context of the Foreman server, so usually under the
|
74
|
-
`foreman` user.
|
75
|
-
|
76
|
-
|
87
|
+
`foreman` user.
|
88
|
+
|
89
|
+
The first argument is always the event name, enabling scripts to be symlinked
|
90
|
+
into multiple event directories. The second argument is the string
|
91
|
+
representation of the object that was hooked, e.g. the hostname for a host.
|
92
|
+
No other data about the object is currently made available.
|
93
|
+
|
94
|
+
Every hook within the event directory is executed in alphabetical order. For
|
95
|
+
orchestration hooks, an integer prefix in the hook filename will be used as
|
96
|
+
the priority value, so influences where it's done in relation to DNS, DHCP, VM
|
97
|
+
creation and other tasks.
|
98
|
+
|
99
|
+
If a hook fails (non-zero return code), the event is logged.
|
100
|
+
For orchestration events, a failure will halt the action and rollback will
|
101
|
+
occur. For Rails events, execution of other hooks will continue.
|
77
102
|
|
78
103
|
# Copyright
|
79
104
|
|
data/TODO
CHANGED
data/foreman_hooks.gemspec
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "foreman_hooks"
|
3
3
|
|
4
|
-
s.version = "0.
|
5
|
-
s.date = "2013-03-
|
4
|
+
s.version = "0.2.0"
|
5
|
+
s.date = "2013-03-24"
|
6
6
|
|
7
7
|
s.summary = "Run custom hook scripts on Foreman events"
|
8
8
|
s.description = "Plugin engine for Foreman that enables running custom hook scripts on Foreman events"
|
data/lib/foreman_hooks/engine.rb
CHANGED
@@ -1,13 +1,24 @@
|
|
1
1
|
require 'foreman_hooks'
|
2
2
|
require 'foreman_hooks/hooks_observer'
|
3
|
+
require 'foreman_hooks/orchestration_hook'
|
3
4
|
|
4
5
|
module ForemanHooks
|
5
6
|
class Engine < ::Rails::Engine
|
6
7
|
config.to_prepare do
|
8
|
+
# Register an observer to all classes with hooks present
|
7
9
|
ForemanHooks::HooksObserver.observed_classes.each do |klass|
|
8
10
|
klass.observers << ForemanHooks::HooksObserver
|
9
11
|
klass.instantiate_observers
|
10
12
|
end
|
13
|
+
|
14
|
+
# Find any orchestration related hooks and register in those classes
|
15
|
+
ForemanHooks::HooksObserver.hooks.each do |klass,events|
|
16
|
+
orchestrate = false
|
17
|
+
events.keys.each do |event|
|
18
|
+
orchestrate = true if ['create', 'update', 'destroy'].include? event
|
19
|
+
end
|
20
|
+
klass.send(:include, ForemanHooks::OrchestrationHook) if orchestrate
|
21
|
+
end
|
11
22
|
end
|
12
23
|
end
|
13
24
|
end
|
@@ -1,54 +1,8 @@
|
|
1
|
+
require 'foreman_hooks/util'
|
2
|
+
|
1
3
|
module ForemanHooks
|
2
4
|
class HooksObserver < ActiveRecord::Observer
|
3
|
-
|
4
|
-
Rails.logger
|
5
|
-
end
|
6
|
-
|
7
|
-
def self.hooks_root
|
8
|
-
File.join(Rails.application.root, 'config', 'hooks')
|
9
|
-
end
|
10
|
-
|
11
|
-
# Find all executable hook files under $hook_root/model_name/event_name/
|
12
|
-
def self.search_hooks
|
13
|
-
hooks = {}
|
14
|
-
Dir.glob(File.join(hooks_root, '**', '*')) do |filename|
|
15
|
-
next if filename.end_with? '~'
|
16
|
-
next if filename.end_with? '.bak'
|
17
|
-
next if File.directory? filename
|
18
|
-
next unless File.executable? filename
|
19
|
-
|
20
|
-
relative = filename[hooks_root.size..-1]
|
21
|
-
next unless relative =~ %r{^/(.+)/([^/]+)/([^/]+)$}
|
22
|
-
klass = $1.camelize.constantize
|
23
|
-
event = $2
|
24
|
-
script_name = $3
|
25
|
-
hooks[klass] ||= {}
|
26
|
-
hooks[klass][event] ||= []
|
27
|
-
hooks[klass][event] << filename
|
28
|
-
logger.debug "Found hook to #{klass.to_s}##{event}, filename #{script_name}"
|
29
|
-
end
|
30
|
-
hooks
|
31
|
-
end
|
32
|
-
|
33
|
-
# {ModelClass => {'event_name' => ['/path/to/01.sh', '/path/to/02.sh']}}
|
34
|
-
def self.hooks
|
35
|
-
unless @hooks
|
36
|
-
@hooks = search_hooks
|
37
|
-
@hooks.each do |klass,events|
|
38
|
-
events.each do |event,hooks|
|
39
|
-
logger.info "Finished adding #{hooks.size} hooks to #{Host::Base.to_s}##{event}"
|
40
|
-
hooks.sort!
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
@hooks
|
45
|
-
end
|
46
|
-
|
47
|
-
# ['event1', 'event2']
|
48
|
-
def self.events
|
49
|
-
@events = hooks.values.map(&:keys).flatten.uniq.map(&:to_sym) unless @events
|
50
|
-
@events
|
51
|
-
end
|
5
|
+
include ForemanHooks::Util
|
52
6
|
|
53
7
|
# Override ActiveRecord::Observer
|
54
8
|
def self.observed_classes
|
@@ -63,30 +17,12 @@ module ForemanHooks
|
|
63
17
|
def method_missing(event, *args)
|
64
18
|
obj = args.first
|
65
19
|
logger.debug "Observed #{event} hook on #{obj}"
|
66
|
-
|
67
|
-
return unless hooks = self.class.hooks[obj.class]
|
68
|
-
return unless hooks = hooks[event.to_s]
|
69
|
-
return if hooks.empty?
|
20
|
+
return unless hooks = find_hooks(obj.class, event)
|
70
21
|
|
71
22
|
logger.debug "Running #{hooks.size} hooks for #{obj.class.to_s}##{event}"
|
72
|
-
hooks.each { |filename| exec_hook(filename, obj.to_s) }
|
23
|
+
hooks.each { |filename| exec_hook(filename, event.to_s, obj.to_s) }
|
73
24
|
end
|
74
25
|
|
75
|
-
def
|
76
|
-
logger.debug "Running hook: #{args.join(' ')}"
|
77
|
-
success = if defined? Bundler && Bundler.responds_to(:with_clean_env)
|
78
|
-
Bundler.with_clean_env { system(*args) }
|
79
|
-
else
|
80
|
-
system(*args)
|
81
|
-
end
|
82
|
-
|
83
|
-
unless success
|
84
|
-
logger.warn "Hook failure running `#{args.join(' ')}`: #{$?}"
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def logger
|
89
|
-
Rails.logger
|
90
|
-
end
|
26
|
+
def logger; Rails.logger; end
|
91
27
|
end
|
92
28
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'foreman_hooks/util'
|
2
|
+
|
3
|
+
module ForemanHooks::OrchestrationHook
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
include ForemanHooks::Util
|
6
|
+
|
7
|
+
included do
|
8
|
+
after_validation :queue_hooks_validate
|
9
|
+
before_destroy :queue_hooks_destroy
|
10
|
+
end
|
11
|
+
|
12
|
+
def queue_hooks_validate
|
13
|
+
return unless errors.empty?
|
14
|
+
queue_hooks(new_record? ? 'create' : 'update')
|
15
|
+
end
|
16
|
+
|
17
|
+
def queue_hooks_destroy
|
18
|
+
return unless errors.empty?
|
19
|
+
queue_hooks('destroy')
|
20
|
+
end
|
21
|
+
|
22
|
+
def queue_hooks(event)
|
23
|
+
logger.debug "Observed #{event} hook on #{self}"
|
24
|
+
unless is_a? Orchestration
|
25
|
+
logger.warn "#{self.class.to_s} doesn't support orchestration, can't run orchestration hooks: use Rails events instead"
|
26
|
+
end
|
27
|
+
|
28
|
+
return unless hooks = find_hooks(self.class, event)
|
29
|
+
logger.debug "Queueing #{hooks.size} hooks for #{self.class.to_s}##{event}"
|
30
|
+
|
31
|
+
counter = 0
|
32
|
+
hooks.each do |filename|
|
33
|
+
basename = File.basename(filename)
|
34
|
+
priority = basename =~ /^(\d+)/ ? $1 : 10000 + (counter += 1)
|
35
|
+
logger.debug "Queuing hook #{basename} for #{self.class.to_s}##{event} at priority #{priority}"
|
36
|
+
queue.create(:name => "Hook: #{basename}", :priority => priority,
|
37
|
+
:action => [HookRunner.new(filename, self, event.to_s),
|
38
|
+
event.to_s == 'destroy' ? :hook_execute_del : :hook_execute_set])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Orchestration runs methods against an object, so generate a runner for each
|
43
|
+
# hook that will need executing
|
44
|
+
class HookRunner
|
45
|
+
def initialize(filename, obj, event)
|
46
|
+
@filename = filename
|
47
|
+
@obj = obj
|
48
|
+
@event = event
|
49
|
+
end
|
50
|
+
|
51
|
+
def args
|
52
|
+
[@obj.to_s]
|
53
|
+
end
|
54
|
+
|
55
|
+
def hook_execute_set
|
56
|
+
@obj.exec_hook(@filename, @event, *args)
|
57
|
+
end
|
58
|
+
|
59
|
+
def hook_execute_del
|
60
|
+
@obj.exec_hook(@filename, 'destroy', *args)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module ForemanHooks::Util
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
class_eval do
|
6
|
+
def self.hooks_root
|
7
|
+
File.join(Rails.application.root, 'config', 'hooks')
|
8
|
+
end
|
9
|
+
|
10
|
+
# Find all executable hook files under $hook_root/model_name/event_name/
|
11
|
+
def self.discover_hooks
|
12
|
+
hooks = {}
|
13
|
+
Dir.glob(File.join(hooks_root, '**', '*')) do |filename|
|
14
|
+
next if filename.end_with? '~'
|
15
|
+
next if filename.end_with? '.bak'
|
16
|
+
next if File.directory? filename
|
17
|
+
next unless File.executable? filename
|
18
|
+
|
19
|
+
relative = filename[hooks_root.size..-1]
|
20
|
+
next unless relative =~ %r{^/(.+)/([^/]+)/([^/]+)$}
|
21
|
+
klass = $1.camelize.constantize
|
22
|
+
event = $2
|
23
|
+
script_name = $3
|
24
|
+
hooks[klass] ||= {}
|
25
|
+
hooks[klass][event] ||= []
|
26
|
+
hooks[klass][event] << filename
|
27
|
+
logger.debug "Found hook to #{klass.to_s}##{event}, filename #{script_name}"
|
28
|
+
end
|
29
|
+
hooks
|
30
|
+
end
|
31
|
+
|
32
|
+
# {ModelClass => {'event_name' => ['/path/to/01.sh', '/path/to/02.sh']}}
|
33
|
+
def self.hooks
|
34
|
+
unless @hooks
|
35
|
+
@hooks = discover_hooks
|
36
|
+
@hooks.each do |klass,events|
|
37
|
+
events.each do |event,hooks|
|
38
|
+
logger.info "Finished registering #{hooks.size} hooks for #{Host::Base.to_s}##{event}"
|
39
|
+
hooks.sort!
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
@hooks
|
44
|
+
end
|
45
|
+
|
46
|
+
# ['event1', 'event2']
|
47
|
+
def self.events
|
48
|
+
@events = hooks.values.map(&:keys).flatten.uniq.map(&:to_sym) unless @events
|
49
|
+
@events
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.logger; Rails.logger; end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def find_hooks(klass, event)
|
57
|
+
return unless filtered = self.class.hooks[klass]
|
58
|
+
return unless filtered = filtered[event.to_s]
|
59
|
+
return if filtered.empty?
|
60
|
+
filtered
|
61
|
+
end
|
62
|
+
|
63
|
+
def exec_hook(*args)
|
64
|
+
logger.debug "Running hook: #{args.join(' ')}"
|
65
|
+
success = if defined? Bundler && Bundler.responds_to(:with_clean_env)
|
66
|
+
Bundler.with_clean_env { system(*args) }
|
67
|
+
else
|
68
|
+
system(*args)
|
69
|
+
end
|
70
|
+
|
71
|
+
unless success
|
72
|
+
logger.warn "Hook failure running `#{args.join(' ')}`: #{$?}"
|
73
|
+
end
|
74
|
+
success
|
75
|
+
end
|
76
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman_hooks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-24 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Plugin engine for Foreman that enables running custom hook scripts on
|
15
15
|
Foreman events
|
@@ -32,6 +32,8 @@ files:
|
|
32
32
|
- lib/foreman_hooks.rb
|
33
33
|
- lib/foreman_hooks/engine.rb
|
34
34
|
- lib/foreman_hooks/hooks_observer.rb
|
35
|
+
- lib/foreman_hooks/orchestration_hook.rb
|
36
|
+
- lib/foreman_hooks/util.rb
|
35
37
|
- test/test_helper.rb
|
36
38
|
- test/unit/host_observer_test.rb
|
37
39
|
homepage: http://github.com/domcleal/foreman_hooks
|