Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Dominic Cleal
committed
Mar 24, 2013
0 parents
commit a89da9b
Showing
14 changed files
with
1,018 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.rvmrc | ||
Gemfile.lock | ||
test/foreman_app | ||
.bundle |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
language: ruby | ||
rvm: | ||
- "1.8.7" | ||
- "1.9.3" | ||
before_install: rake test:foreman_prepare | ||
script: rake test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
source "http://rubygems.org" | ||
|
||
FOREMAN_GEMFILE=File.expand_path('../test/foreman_app/Gemfile', __FILE__) | ||
unless File.exist?(FOREMAN_GEMFILE) | ||
puts <<MESSAGE | ||
Foreman source code is not present. To get the latest version, run: | ||
rake test:foreman_prepare | ||
and try again. | ||
MESSAGE | ||
|
||
else | ||
self.instance_eval(Bundler.read_file(FOREMAN_GEMFILE)) | ||
end |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
# foreman_hooks | ||
|
||
Allows you to trigger scripts and commands on the Foreman server at any point | ||
in an object's lifecycle in Foreman. This lets you run a script when a host | ||
is created, or finishes provisioning etc. | ||
|
||
It observes every object in Foreman and exposes the Rails callbacks by running | ||
scripts within its hooks directory. | ||
|
||
# Installation: | ||
|
||
Include in your `~foreman/bundler.d/foreman_hooks.rb` | ||
|
||
gem 'foreman_hooks' | ||
|
||
Or from git: | ||
|
||
gem 'foreman_hooks', :git => "https://github.com/domcleal/foreman_hooks.git" | ||
|
||
Regenerate Gemfile.lock: | ||
|
||
cd ~foreman && sudo -u foreman bundle install | ||
|
||
To upgrade to newest version of the plugin: | ||
|
||
cd ~foreman && sudo -u foreman bundle update foreman_hooks | ||
|
||
# Usage | ||
|
||
Hooks are stored in `/usr/share/foreman/config/hooks` (`~foreman/config/hooks`) | ||
with a subdirectory for the object, then a subdirectory for the event name. | ||
Each file within the directory is executed in alphabetical order. | ||
|
||
Examples: | ||
|
||
~foreman/config/hooks/smart_proxy/after_create/01_email_operations.sh | ||
~foreman/config/hooks/host/before_provision/50_do_something.sh | ||
~foreman/config/hooks/host/managed/after_destroy/15_cleanup_database.sh | ||
|
||
Note that in Foreman 1.1, hosts are just named `Host` so hooks go in a `host/` | ||
directory, while in Foreman 1.2 they're `Host::Base` and `Host::Managed`, so | ||
the hook directory becomes `host/base/` and `host/managed/` respectively. | ||
|
||
## Objects / Models | ||
|
||
Every object (or model in Rails terms) in Foreman can have hooks. Check | ||
`~foreman/app/models` for the full list, but these are the interesting ones: | ||
|
||
* `host` (Foreman 1.1), `host/managed` (Foreman 1.2) | ||
* `host/discovered` (Foreman 1.2) | ||
* `report` | ||
|
||
## Events | ||
|
||
These are the most interesting events that Rails provides and this plugin | ||
exposes: | ||
|
||
* `after_create` | ||
* `after_destroy` | ||
|
||
Every event has a "before" and "after" hook. For the full list, see the | ||
Constants section at the bottom of the | ||
[ActiveRecord::Callbacks](http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html) | ||
documentation. | ||
|
||
The host object has two special callbacks in Foreman 1.1 that you can use: | ||
|
||
* `host/after_build` triggers when a host is put into Build mode(??) | ||
* `host/before_provision` triggers... (??) | ||
|
||
## Execution of hooks | ||
|
||
Hooks are executed in the context of the Foreman server, so usually under the | ||
`foreman` user. One argument is provided, which is the string representation | ||
of the object that was hooked, e.g. the hostname for a host. No other data | ||
about the object is currently made available. | ||
|
||
# Copyright | ||
|
||
Copyright (c) 2012-2013 Red Hat Inc. | ||
|
||
This program is free software: you can redistribute it and/or modify | ||
it under the terms of the GNU General Public License as published by | ||
the Free Software Foundation, either version 3 of the License, or | ||
(at your option) any later version. | ||
|
||
This program is distributed in the hope that it will be useful, | ||
but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
GNU General Public License for more details. | ||
|
||
You should have received a copy of the GNU General Public License | ||
along with this program. If not, see <http://www.gnu.org/licenses/>. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
# encoding: utf-8 | ||
|
||
require 'rubygems' | ||
require 'rake' | ||
require 'fileutils' | ||
|
||
task :default => :test | ||
|
||
ENGINE_DIR = File.expand_path('..', __FILE__) | ||
FOREMAN_DIR = 'test/foreman_app' | ||
|
||
namespace :test do | ||
desc "Download latest foreman devel source and install dependencies" | ||
task :foreman_prepare do | ||
foreman_repo = 'https://github.com/theforeman/foreman.git' | ||
foreman_gemfile = File.join(FOREMAN_DIR, "Gemfile") | ||
unless File.exists?(foreman_gemfile) | ||
puts "Foreman source code is not present at #{FOREMAN_DIR}" | ||
puts "Downloading latest Foreman development branch into #{FOREMAN_DIR}..." | ||
FileUtils.mkdir_p(FOREMAN_DIR) | ||
|
||
unless system("git clone #{foreman_repo} #{FOREMAN_DIR}") | ||
puts "Error while getting latest Foreman code from #{foreman_repo} into #{FOREMAN_DIR}" | ||
fail | ||
end | ||
end | ||
|
||
gemfile_content = File.read(foreman_gemfile) | ||
unless gemfile_content.include?('FOREMAN_GEMFILE') | ||
puts 'Preparing Gemfile' | ||
gemfile_content.gsub!('__FILE__', 'FOREMAN_GEMFILE') | ||
gemfile_content.insert(0, "FOREMAN_GEMFILE = __FILE__ unless defined? FOREMAN_GEMFILE\n") | ||
File.open(foreman_gemfile, 'w') { |f| f << gemfile_content } | ||
end | ||
|
||
settings_file = "#{FOREMAN_DIR}/config/settings.yaml" | ||
unless File.exists?(settings_file) | ||
puts 'Preparing settings file' | ||
FileUtils.copy("#{settings_file}.example", settings_file) | ||
settings_content = File.read(settings_file) | ||
settings_content.sub!('organizations_enabled: false', 'organizations_enabled: true') | ||
settings_content << ":puppetgem: true\n" | ||
File.open(settings_file, 'w') { |f| f << settings_content } | ||
end | ||
|
||
db_file = "#{FOREMAN_DIR}/config/database.yml" | ||
unless File.exists?(db_file) | ||
puts 'Preparing database file' | ||
FileUtils.copy("#{db_file}.example", db_file) | ||
end | ||
|
||
["#{ENGINE_DIR}/.bundle/config", "#{FOREMAN_DIR}/.bundle/config"].each do |bundle_file| | ||
unless File.exists?(bundle_file) | ||
FileUtils.mkdir_p(File.dirname(bundle_file)) | ||
puts 'Preparing bundler configuration' | ||
File.open(bundle_file, 'w') { |f| f << <<FILE } | ||
--- | ||
BUNDLE_WITHOUT: console:development:fog:jsonp:libvirt:mysql:mysql2:ovirt:postgresql:vmware | ||
FILE | ||
end | ||
end | ||
|
||
local_gemfile = "#{FOREMAN_DIR}/bundler.d/Gemfile.local.rb" | ||
unless File.exist?(local_gemfile) | ||
File.open(local_gemfile, 'w') { |f| f << <<GEMFILE } | ||
gem "puppet" | ||
gem "facter" | ||
GEMFILE | ||
end | ||
|
||
puts 'Installing dependencies...' | ||
unless system('bundle install') | ||
fail | ||
end | ||
end | ||
|
||
task :db_prepare do | ||
unless File.exists?(FOREMAN_DIR) | ||
puts <<MESSAGE | ||
Foreman source code not prepared. Run | ||
rake test:foreman_prepare | ||
to download foreman source and its dependencies | ||
MESSAGE | ||
fail | ||
end | ||
|
||
# once we are Ruby19 only, switch to block variant of cd | ||
pwd = FileUtils.pwd | ||
FileUtils.cd(FOREMAN_DIR) | ||
unless system('rake db:test:prepare RAILS_ENV=test') | ||
puts "Migrating database first" | ||
system('rake db:migrate db:schema:dump db:test:prepare RAILS_ENV=test') || fail | ||
end | ||
FileUtils.cd(pwd) | ||
end | ||
|
||
task :set_loadpath do | ||
%w[lib test].each do |dir| | ||
$:.unshift(File.expand_path(dir, ENGINE_DIR)) | ||
end | ||
end | ||
|
||
task :all => [:db_prepare, :set_loadpath] do | ||
Dir.glob('test/**/*_test.rb') { |f| require f.sub('test/','') unless f.include? '/foreman_app/' } | ||
end | ||
|
||
end | ||
|
||
task :test => 'test:all' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
* pass more data into hooks | ||
* JSON dump of the model via stdin + utility shell script | ||
* tie into orchestration |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
Gem::Specification.new do |s| | ||
s.name = "foreman_hooks" | ||
|
||
s.version = "0.1.0" | ||
s.date = "2013-03-23" | ||
|
||
s.summary = "Run custom hook scripts on Foreman events" | ||
s.description = "Plugin engine for Foreman that enables running custom hook scripts on Foreman events" | ||
s.homepage = "http://github.com/domcleal/foreman_hooks" | ||
s.licenses = ["GPL-3"] | ||
s.require_paths = ["lib"] | ||
|
||
s.authors = ["Dominic Cleal"] | ||
s.email = "dcleal@redhat.com" | ||
|
||
s.extra_rdoc_files = [ | ||
"LICENSE", | ||
"README.md", | ||
"TODO" | ||
] | ||
s.files = `git ls-files`.split("\n") | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module ForemanHooks | ||
require 'foreman_hooks/engine' if defined?(Rails) && Rails::VERSION::MAJOR == 3 | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
require 'foreman_hooks' | ||
require 'foreman_hooks/hooks_observer' | ||
|
||
module ForemanHooks | ||
class Engine < ::Rails::Engine | ||
config.to_prepare do | ||
ForemanHooks::HooksObserver.observed_classes.each do |klass| | ||
klass.observers << ForemanHooks::HooksObserver | ||
klass.instantiate_observers | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
module ForemanHooks | ||
class HooksObserver < ActiveRecord::Observer | ||
def self.logger | ||
Rails.logger | ||
end | ||
|
||
def self.hooks_root | ||
File.join(Rails.application.root, 'config', 'hooks') | ||
end | ||
|
||
# Find all executable hook files under $hook_root/model_name/event_name/ | ||
def self.search_hooks | ||
hooks = {} | ||
Dir.glob(File.join(hooks_root, '**', '*')) do |filename| | ||
next if filename.end_with? '~' | ||
next if filename.end_with? '.bak' | ||
next if File.directory? filename | ||
next unless File.executable? filename | ||
|
||
relative = filename[hooks_root.size..-1] | ||
next unless relative =~ %r{^/(.+)/([^/]+)/([^/]+)$} | ||
klass = $1.camelize.constantize | ||
event = $2 | ||
script_name = $3 | ||
hooks[klass] ||= {} | ||
hooks[klass][event] ||= [] | ||
hooks[klass][event] << filename | ||
logger.debug "Found hook to #{klass.to_s}##{event}, filename #{script_name}" | ||
end | ||
hooks | ||
end | ||
|
||
# {ModelClass => {'event_name' => ['/path/to/01.sh', '/path/to/02.sh']}} | ||
def self.hooks | ||
unless @hooks | ||
@hooks = search_hooks | ||
@hooks.each do |klass,events| | ||
events.each do |event,hooks| | ||
logger.info "Finished adding #{hooks.size} hooks to #{Host::Base.to_s}##{event}" | ||
hooks.sort! | ||
end | ||
end | ||
end | ||
@hooks | ||
end | ||
|
||
# ['event1', 'event2'] | ||
def self.events | ||
@events = hooks.values.map(&:keys).flatten.uniq.map(&:to_sym) unless @events | ||
@events | ||
end | ||
|
||
# Override ActiveRecord::Observer | ||
def self.observed_classes | ||
hooks.keys | ||
end | ||
|
||
def respond_to?(method) | ||
return true if super | ||
self.class.events.include? method | ||
end | ||
|
||
def method_missing(event, *args) | ||
obj = args.first | ||
logger.debug "Observed #{event} hook on #{obj}" | ||
|
||
return unless hooks = self.class.hooks[obj.class] | ||
return unless hooks = hooks[event.to_s] | ||
return if hooks.empty? | ||
|
||
logger.debug "Running #{hooks.size} hooks for #{obj.class.to_s}##{event}" | ||
hooks.each { |filename| exec_hook(filename, obj.to_s) } | ||
end | ||
|
||
def exec_hook(*args) | ||
logger.debug "Running hook: #{args.join(' ')}" | ||
success = if defined? Bundler && Bundler.responds_to(:with_clean_env) | ||
Bundler.with_clean_env { system(*args) } | ||
else | ||
system(*args) | ||
end | ||
|
||
unless success | ||
logger.warn "Hook failure running `#{args.join(' ')}`: #{$?}" | ||
end | ||
end | ||
|
||
def logger | ||
Rails.logger | ||
end | ||
end | ||
end |
Oops, something went wrong.