foreman_hooks 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|