loom-core 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +99 -0
- data/Guardfile +54 -0
- data/Rakefile +6 -0
- data/bin/loom +185 -0
- data/lib/env/development.rb +1 -0
- data/lib/loom.rb +44 -0
- data/lib/loom/all.rb +20 -0
- data/lib/loom/config.rb +106 -0
- data/lib/loom/core_ext.rb +37 -0
- data/lib/loom/dsl.rb +60 -0
- data/lib/loom/facts.rb +13 -0
- data/lib/loom/facts/all.rb +2 -0
- data/lib/loom/facts/fact_file_provider.rb +86 -0
- data/lib/loom/facts/fact_set.rb +138 -0
- data/lib/loom/host_spec.rb +32 -0
- data/lib/loom/inventory.rb +124 -0
- data/lib/loom/logger.rb +141 -0
- data/lib/loom/method_signature.rb +174 -0
- data/lib/loom/mods.rb +4 -0
- data/lib/loom/mods/action_proxy.rb +105 -0
- data/lib/loom/mods/all.rb +3 -0
- data/lib/loom/mods/mod_loader.rb +80 -0
- data/lib/loom/mods/module.rb +113 -0
- data/lib/loom/pattern.rb +15 -0
- data/lib/loom/pattern/all.rb +7 -0
- data/lib/loom/pattern/definition_context.rb +74 -0
- data/lib/loom/pattern/dsl.rb +176 -0
- data/lib/loom/pattern/hook.rb +28 -0
- data/lib/loom/pattern/loader.rb +48 -0
- data/lib/loom/pattern/reference.rb +71 -0
- data/lib/loom/pattern/reference_set.rb +169 -0
- data/lib/loom/pattern/result_reporter.rb +77 -0
- data/lib/loom/runner.rb +209 -0
- data/lib/loom/shell.rb +12 -0
- data/lib/loom/shell/all.rb +10 -0
- data/lib/loom/shell/api.rb +48 -0
- data/lib/loom/shell/cmd_result.rb +33 -0
- data/lib/loom/shell/cmd_wrapper.rb +164 -0
- data/lib/loom/shell/core.rb +226 -0
- data/lib/loom/shell/harness_blob.rb +26 -0
- data/lib/loom/shell/harness_command_builder.rb +50 -0
- data/lib/loom/shell/session.rb +25 -0
- data/lib/loom/trap.rb +44 -0
- data/lib/loom/version.rb +3 -0
- data/lib/loomext/all.rb +4 -0
- data/lib/loomext/corefacts.rb +6 -0
- data/lib/loomext/corefacts/all.rb +8 -0
- data/lib/loomext/corefacts/facter_provider.rb +24 -0
- data/lib/loomext/coremods.rb +5 -0
- data/lib/loomext/coremods/all.rb +13 -0
- data/lib/loomext/coremods/exec.rb +50 -0
- data/lib/loomext/coremods/files.rb +104 -0
- data/lib/loomext/coremods/net.rb +33 -0
- data/lib/loomext/coremods/package/adapter.rb +100 -0
- data/lib/loomext/coremods/package/package.rb +62 -0
- data/lib/loomext/coremods/user.rb +82 -0
- data/lib/loomext/coremods/vm.rb +0 -0
- data/lib/loomext/coremods/vm/all.rb +6 -0
- data/lib/loomext/coremods/vm/vbox.rb +84 -0
- data/loom.gemspec +39 -0
- data/loom/inventory.yml +13 -0
- data/scripts/harness.sh +242 -0
- data/spec/loom/host_spec_spec.rb +101 -0
- data/spec/loom/inventory_spec.rb +154 -0
- data/spec/loom/method_signature_spec.rb +275 -0
- data/spec/loom/pattern/dsl_spec.rb +207 -0
- data/spec/loom/shell/cmd_wrapper_spec.rb +239 -0
- data/spec/loom/shell/harness_blob_spec.rb +42 -0
- data/spec/loom/shell/harness_command_builder_spec.rb +36 -0
- data/spec/runloom.sh +35 -0
- data/spec/scripts/harness_spec.rb +385 -0
- data/spec/spec_helper.rb +94 -0
- data/spec/test.loom +370 -0
- data/spec/test_loom_spec.rb +57 -0
- metadata +287 -0
@@ -0,0 +1,71 @@
|
|
1
|
+
module Loom::Pattern
|
2
|
+
class Reference
|
3
|
+
|
4
|
+
attr_reader :slug, :source_file, :desc
|
5
|
+
|
6
|
+
def initialize(slug, unbound_method, source_file, definition_ctx, description)
|
7
|
+
@slug = slug
|
8
|
+
@unbound_method = unbound_method
|
9
|
+
@source_file = source_file
|
10
|
+
@definition_ctx = definition_ctx
|
11
|
+
@desc = description
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(shell_api, host_fact_set)
|
15
|
+
run_context = RunContext.new @unbound_method, @definition_ctx
|
16
|
+
|
17
|
+
fact_set = @definition_ctx.fact_set host_fact_set
|
18
|
+
Loom.log.debug5(self) { "fact set for pattern execution => #{fact_set.facts}" }
|
19
|
+
|
20
|
+
@definition_ctx.define_let_readers run_context, fact_set
|
21
|
+
|
22
|
+
begin
|
23
|
+
run_context.run shell_api, fact_set
|
24
|
+
rescue => e
|
25
|
+
error_msg = "error executing '#{slug}' in #{source_file} => #{e} \n%s"
|
26
|
+
Loom.log.error(error_msg % e.backtrace.join("\n\t"))
|
27
|
+
raise
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
##
|
34
|
+
# A small class to bind the unbound_method to and provide context
|
35
|
+
# in the case of errors.
|
36
|
+
class RunContext
|
37
|
+
def initialize(unbound_method, definition_ctx)
|
38
|
+
@bound_method = unbound_method.bind self
|
39
|
+
@definition_ctx = definition_ctx
|
40
|
+
end
|
41
|
+
|
42
|
+
def run(shell_api, fact_set)
|
43
|
+
before_hooks = @definition_ctx.before_hooks
|
44
|
+
after_hooks = @definition_ctx.after_hooks
|
45
|
+
|
46
|
+
begin
|
47
|
+
Loom.log.debug1(self) { "before hooks => #{before_hooks}"}
|
48
|
+
before_hooks.each do |hook|
|
49
|
+
Loom.log.debug2(self) { "executing before hook => #{hook}"}
|
50
|
+
self.instance_exec shell_api, fact_set, &hook.block
|
51
|
+
end
|
52
|
+
|
53
|
+
# This is the entry point into calling patterns.
|
54
|
+
apply_pattern shell_api, fact_set
|
55
|
+
ensure
|
56
|
+
Loom.log.debug1(self) { "after hooks => #{after_hooks}" }
|
57
|
+
after_hooks.each do |hook|
|
58
|
+
Loom.log.debug2(self) { "executing after hook => #{hook}"}
|
59
|
+
self.instance_exec shell_api, fact_set, &hook.block
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
def apply_pattern(*args)
|
66
|
+
@bound_method.call *args
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
module Loom::Pattern
|
2
|
+
|
3
|
+
DuplicatePatternRef = Class.new Loom::LoomError
|
4
|
+
UnknownPatternMethod = Class.new Loom::LoomError
|
5
|
+
InvalidPatternNamespace = Class.new Loom::LoomError
|
6
|
+
|
7
|
+
##
|
8
|
+
# A collection of Pattern::Reference objects
|
9
|
+
class ReferenceSet
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def load_from_file(path)
|
13
|
+
Loom.log.debug1(self) { "loading patterns from file => #{path}" }
|
14
|
+
Builder.create File.read(path), path
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@slug_to_ref_map = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def slugs
|
23
|
+
@slug_to_ref_map.keys
|
24
|
+
end
|
25
|
+
|
26
|
+
def pattern_refs
|
27
|
+
@slug_to_ref_map.values
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_pattern_ref(slug)
|
31
|
+
ref = @slug_to_ref_map[slug]
|
32
|
+
raise UnknownPatternMethod, slug unless ref
|
33
|
+
ref
|
34
|
+
end
|
35
|
+
alias_method :[], :get_pattern_ref
|
36
|
+
|
37
|
+
def merge!(ref_set)
|
38
|
+
self.add_pattern_refs(ref_set.pattern_refs)
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_pattern_refs(refs)
|
42
|
+
map = @slug_to_ref_map
|
43
|
+
refs.each do |ref|
|
44
|
+
Loom.log.debug2(self) { "adding ref to set => #{ref.slug}" }
|
45
|
+
raise DuplicatePatternRef, ref.slug if map[ref.slug]
|
46
|
+
map[ref.slug] = ref
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Builder
|
51
|
+
using Loom::CoreExt # using demodulize for namespace creation
|
52
|
+
|
53
|
+
class << self
|
54
|
+
def create(ruby_code, source)
|
55
|
+
shell_module = Module.new
|
56
|
+
shell_module.include Loom::Pattern
|
57
|
+
shell_module.module_eval ruby_code, source, 1
|
58
|
+
shell_module.namespace ""
|
59
|
+
|
60
|
+
self.new(shell_module, source).build
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def initialize(shell_module, source)
|
65
|
+
@shell_module = shell_module
|
66
|
+
@pattern_mod_specs = pattern_mod_specs
|
67
|
+
@source = source
|
68
|
+
end
|
69
|
+
|
70
|
+
def build
|
71
|
+
ref_set = ReferenceSet.new
|
72
|
+
ref_set.add_pattern_refs pattern_refs
|
73
|
+
ref_set
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
def pattern_refs
|
78
|
+
@pattern_mod_specs.map { |mod_spec| refs_for_mod_spec mod_spec }.flatten
|
79
|
+
end
|
80
|
+
|
81
|
+
def refs_for_mod_spec(mod_spec)
|
82
|
+
mod = mod_spec[:module]
|
83
|
+
context = context_for_mod_spec mod_spec
|
84
|
+
source = @source
|
85
|
+
|
86
|
+
mod_spec[:pattern_methods].map do |m|
|
87
|
+
method = mod.pattern_method m
|
88
|
+
desc = mod.pattern_description m
|
89
|
+
slug = compute_slug mod_spec[:namespace_list], m
|
90
|
+
|
91
|
+
Loom.log.warn "no descripiton for pattern => #{slug}" unless desc
|
92
|
+
Reference.new slug, method, source, context, desc
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def context_for_mod_spec(mod_spec)
|
97
|
+
parents = mod_spec[:parent_modules].find_all do |mod|
|
98
|
+
is_pattern_module mod
|
99
|
+
end
|
100
|
+
parent_context = parents.reduce(nil) do |parent_ctx, parent_mod|
|
101
|
+
DefinitionContext.new parent_mod, parent_ctx
|
102
|
+
end
|
103
|
+
|
104
|
+
mod = mod_spec[:module]
|
105
|
+
DefinitionContext.new mod, parent_context
|
106
|
+
end
|
107
|
+
|
108
|
+
def compute_slug(namespace_list, pattern_method_name)
|
109
|
+
namespace_list.dup.push(pattern_method_name).join ":"
|
110
|
+
end
|
111
|
+
|
112
|
+
def mod_namespace_list(pattern, parent_modules)
|
113
|
+
mods = parent_modules.dup << pattern
|
114
|
+
mods.reduce([]) do |memo, mod|
|
115
|
+
mod_name = if mod.respond_to?(:namespace) && mod.namespace
|
116
|
+
mod.namespace
|
117
|
+
else
|
118
|
+
mod.name.demodulize rescue ''
|
119
|
+
end
|
120
|
+
if memo.size > 0 && mod_name.empty?
|
121
|
+
raise InvalidPatternNamespace, "only the root can have an empty namespace"
|
122
|
+
end
|
123
|
+
memo << mod_name.downcase unless mod_name.empty?
|
124
|
+
memo
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def pattern_mod_specs
|
129
|
+
pattern_mods = []
|
130
|
+
traverse_pattern_modules @shell_module do |pattern_mod, parent_modules|
|
131
|
+
Loom.log.debug2(self) { "found pattern module => #{pattern_mod}" }
|
132
|
+
pattern_methods = pattern_mod.pattern_methods
|
133
|
+
|
134
|
+
next if pattern_methods.empty?
|
135
|
+
pattern_mods << {
|
136
|
+
:namespace_list => mod_namespace_list(pattern_mod, parent_modules),
|
137
|
+
:pattern_methods => pattern_methods,
|
138
|
+
:module => pattern_mod,
|
139
|
+
:parent_modules => parent_modules.dup
|
140
|
+
}
|
141
|
+
end
|
142
|
+
pattern_mods
|
143
|
+
end
|
144
|
+
|
145
|
+
def is_pattern_module(mod)
|
146
|
+
mod.included_modules.include? Loom::Pattern
|
147
|
+
end
|
148
|
+
|
149
|
+
def traverse_pattern_modules(mod, pattern_parents=[], visited={}, &block)
|
150
|
+
return if visited[mod.name] # prevent cycles
|
151
|
+
visited[mod.name] = true
|
152
|
+
|
153
|
+
yield mod, pattern_parents.dup if is_pattern_module(mod)
|
154
|
+
|
155
|
+
# Traverse all sub modules, even ones that aren't
|
156
|
+
# Loom::Pattern[s], since they might contain more sub modules
|
157
|
+
# themselves.
|
158
|
+
sub_modules = mod.constants
|
159
|
+
.map { |c| mod.const_get(c) }
|
160
|
+
.find_all { |m| m.is_a? Module }
|
161
|
+
|
162
|
+
pattern_parents << mod
|
163
|
+
sub_modules.each do |sub_mod|
|
164
|
+
traverse_pattern_modules sub_mod, pattern_parents.dup, visited, &block
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Loom::Pattern
|
2
|
+
class ResultReporter
|
3
|
+
def initialize(loom_config, pattern_slug, hostname, shell_session)
|
4
|
+
@loom_config = loom_config
|
5
|
+
@start = Time.now
|
6
|
+
@delta_t = nil
|
7
|
+
@hostname = hostname
|
8
|
+
@pattern_slug = pattern_slug
|
9
|
+
@shell_session = shell_session
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :hostname
|
13
|
+
|
14
|
+
def failure_summary
|
15
|
+
return "scenario did not fail" if success?
|
16
|
+
scenario_string
|
17
|
+
end
|
18
|
+
|
19
|
+
def write_report
|
20
|
+
@delta_t = Time.now - @start
|
21
|
+
|
22
|
+
report = generate_report.join "\n\t"
|
23
|
+
if success?
|
24
|
+
Loom.log.info report
|
25
|
+
else
|
26
|
+
Loom.log.warn report
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def success?
|
32
|
+
@shell_session.success?
|
33
|
+
end
|
34
|
+
|
35
|
+
def scenario_string
|
36
|
+
status = success? ? "OK" : "FAILED"
|
37
|
+
"#{hostname} => #{@pattern_slug} [Result: #{status}] "
|
38
|
+
end
|
39
|
+
|
40
|
+
def generate_report
|
41
|
+
cmds = @shell_session.command_results
|
42
|
+
|
43
|
+
report = ["--- #{scenario_string}"]
|
44
|
+
report << "Completed in: %01.3fs" % @delta_t
|
45
|
+
|
46
|
+
cmds.find_all { |cmd| !cmd.is_test }.each do |cmd|
|
47
|
+
if !cmd.success? || @loom_config.run_verbose
|
48
|
+
report.concat generate_cmd_report(cmd)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
report
|
53
|
+
end
|
54
|
+
|
55
|
+
def generate_cmd_report(cmd)
|
56
|
+
status = cmd.success? ? "Success" : "Failed"
|
57
|
+
|
58
|
+
report = []
|
59
|
+
report << ""
|
60
|
+
report << "--- #{status} Command ---"
|
61
|
+
report << "$ #{cmd.command}"
|
62
|
+
|
63
|
+
unless cmd.stdout.empty?
|
64
|
+
report << cmd.stdout
|
65
|
+
end
|
66
|
+
|
67
|
+
unless cmd.stderr.empty?
|
68
|
+
report << "[STDERR]:"
|
69
|
+
report << cmd.stderr
|
70
|
+
end
|
71
|
+
|
72
|
+
report << "[EXIT STATUS]: #{cmd.exit_status}"
|
73
|
+
|
74
|
+
report
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/loom/runner.rb
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
module Loom
|
2
|
+
class Runner
|
3
|
+
|
4
|
+
PatternExecutionError = Class.new Loom::LoomError
|
5
|
+
FailFastExecutionError = Class.new PatternExecutionError
|
6
|
+
|
7
|
+
include Loom::DSL
|
8
|
+
|
9
|
+
def initialize(loom_config, pattern_slugs=[], other_facts={})
|
10
|
+
@pattern_slugs = pattern_slugs
|
11
|
+
@loom_config = loom_config
|
12
|
+
@other_facts = other_facts
|
13
|
+
|
14
|
+
@run_failures = []
|
15
|
+
@result_reports = []
|
16
|
+
|
17
|
+
# these are initialized in +load+
|
18
|
+
@inventory_list = nil
|
19
|
+
@active_hosts = nil
|
20
|
+
@pattern_refs = nil
|
21
|
+
@mod_loader = nil
|
22
|
+
|
23
|
+
Loom.log.debug1(self) do
|
24
|
+
"initialized runner with config => #{loom_config.dump}"
|
25
|
+
end
|
26
|
+
|
27
|
+
@caught_sig_int = false
|
28
|
+
end
|
29
|
+
|
30
|
+
def run(dry_run)
|
31
|
+
install_signal_traps
|
32
|
+
|
33
|
+
begin
|
34
|
+
load
|
35
|
+
|
36
|
+
if @pattern_refs.empty?
|
37
|
+
Loom.log.warn "no patterns given, there's no work to do"
|
38
|
+
return
|
39
|
+
end
|
40
|
+
if @active_hosts.empty?
|
41
|
+
Loom.log.warn "no hosts in the active inventory"
|
42
|
+
return
|
43
|
+
end
|
44
|
+
|
45
|
+
hostnames = @active_hosts.map(&:hostname)
|
46
|
+
Loom.log.info do
|
47
|
+
"executing patterns #{@pattern_slugs} across hosts #{hostnames}"
|
48
|
+
end
|
49
|
+
|
50
|
+
run_internal dry_run
|
51
|
+
|
52
|
+
unless @run_failures.empty?
|
53
|
+
raise PatternExecutionError, @run_failures
|
54
|
+
end
|
55
|
+
rescue Loom::Trap::SignalExit => e
|
56
|
+
Loom.log.error "exiting on signal => #{e.signal}"
|
57
|
+
# Exit with the signal code or 40 for unknown Signal
|
58
|
+
code = Signal.list[e.signal] || 40
|
59
|
+
exit code
|
60
|
+
rescue PatternExecutionError => e
|
61
|
+
num_patterns_failed = @run_failures.size
|
62
|
+
Loom.log.error "error executing #{num_patterns_failed} patterns => #{e}"
|
63
|
+
Loom.log.debug e.backtrace.join "\n"
|
64
|
+
exit 100 + num_patterns_failed
|
65
|
+
rescue Loom::LoomError => e
|
66
|
+
Loom.log.error "loom error => #{e.inspect}"
|
67
|
+
exit 98
|
68
|
+
rescue => e
|
69
|
+
Loom.log.fatal "fatal error => #{e.inspect}"
|
70
|
+
Loom.log.fatal e.backtrace.join "\n\t"
|
71
|
+
exit 99
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def install_signal_traps
|
78
|
+
signal_handler = Loom::Trap::Handler.new do |sig, count|
|
79
|
+
case sig
|
80
|
+
when Loom::Trap::Sig::INT
|
81
|
+
@caught_sig_int = true
|
82
|
+
if count == 1
|
83
|
+
puts "Caught #{sig}, exiting after current pattern completion"
|
84
|
+
puts "Ctrl-C again to exit immediately"
|
85
|
+
else
|
86
|
+
puts "Caught #{sig}"
|
87
|
+
raise Loom::Trap::SignalExit.new sig
|
88
|
+
end
|
89
|
+
else
|
90
|
+
puts "Caught unhandled signal #{sig}"
|
91
|
+
raise Loom::Trap::SignalExit.new sig
|
92
|
+
end
|
93
|
+
end
|
94
|
+
Loom::Trap.install(Loom::Trap::Sig::INT, signal_handler)
|
95
|
+
end
|
96
|
+
|
97
|
+
def load
|
98
|
+
@inventory_list =
|
99
|
+
Loom::Inventory::InventoryList.active_inventory @loom_config
|
100
|
+
@active_hosts = @inventory_list.hosts
|
101
|
+
|
102
|
+
pattern_loader = Loom::Pattern::Loader.load @loom_config
|
103
|
+
@pattern_refs = pattern_loader.patterns @pattern_slugs
|
104
|
+
|
105
|
+
@mod_loader = Loom::Mods::ModLoader.new @loom_config
|
106
|
+
end
|
107
|
+
|
108
|
+
def run_internal(dry_run)
|
109
|
+
# TODO: fix the bindings in the block below so we don't need
|
110
|
+
# this alias
|
111
|
+
inventory_list = @inventory_list
|
112
|
+
|
113
|
+
on_host @active_hosts do |sshkit_backend, host_spec|
|
114
|
+
hostname = host_spec.hostname
|
115
|
+
|
116
|
+
begin
|
117
|
+
@pattern_refs.each do |pattern_ref|
|
118
|
+
slug = pattern_ref.slug
|
119
|
+
pattern_description = "[#{hostname} => #{slug}]"
|
120
|
+
|
121
|
+
if @caught_sig_int
|
122
|
+
Loom.log.warn "caught SIGINT, skipping #{pattern_description}"
|
123
|
+
next
|
124
|
+
elsif inventory_list.disabled? hostname
|
125
|
+
Loom.log.warn "host disabled due to previous failure, " +
|
126
|
+
"skipping: #{pattern_description}"
|
127
|
+
next
|
128
|
+
end
|
129
|
+
|
130
|
+
Loom.log.debug "collecting facts for => #{pattern_description}"
|
131
|
+
# Collect facts for each pattern run on each host, this way if one
|
132
|
+
# pattern run updates would be facts, the next pattern will see the
|
133
|
+
# new fact.
|
134
|
+
fact_shell = Loom::Shell.create @mod_loader, sshkit_backend, dry_run
|
135
|
+
fact_set = Loom::Facts.fact_set(host_spec, fact_shell, @loom_config)
|
136
|
+
.merge @other_facts
|
137
|
+
|
138
|
+
Loom.log.info "running pattern => #{pattern_description}"
|
139
|
+
# Each pattern execution needs its own shell and mod loader to make
|
140
|
+
# sure context is reported correctly (this is probably a hack, there
|
141
|
+
# should just be a way to clear/ignore state from certain commands -
|
142
|
+
# like the fact_finding ones above).
|
143
|
+
pattern_shell = Loom::Shell.create @mod_loader, sshkit_backend, dry_run
|
144
|
+
|
145
|
+
Loom.log.warn "dry run only => #{pattern_description}" if dry_run
|
146
|
+
execute_pattern pattern_ref, pattern_shell, fact_set
|
147
|
+
end
|
148
|
+
rescue IOError => e
|
149
|
+
# TODO: Try to patch SSHKit for a more specific error for unexpected SSH
|
150
|
+
# disconnections
|
151
|
+
Loom.log.error "unexpected SSH disconnect => #{hostname}"
|
152
|
+
Loom.log.debug e
|
153
|
+
handle_host_failure_strategy hostname, e.message
|
154
|
+
rescue Errno::ECONNREFUSED => e
|
155
|
+
Loom.log.error "unable to connect to host => #{hostname}"
|
156
|
+
Loom.log.debug e
|
157
|
+
handle_host_failure_strategy hostname, e.message
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def execute_pattern(pattern_ref, shell, fact_set)
|
163
|
+
shell_session = shell.session
|
164
|
+
hostname = fact_set.hostname
|
165
|
+
result_reporter = Loom::Pattern::ResultReporter.new(
|
166
|
+
@loom_config, pattern_ref.slug, hostname, shell_session)
|
167
|
+
|
168
|
+
# TODO: This is a crappy mechanism for tracking errors, there should be an
|
169
|
+
# exception thrown inside of Shell when a command fails and pattern
|
170
|
+
# execution should stop. All errors should come from exceptions.
|
171
|
+
run_failure = []
|
172
|
+
begin
|
173
|
+
pattern_ref.call(shell.shell_api, fact_set)
|
174
|
+
rescue Loom::ExecutionError => e
|
175
|
+
Loom.log.debug e.backtrace.join "\n\t"
|
176
|
+
run_failure << e
|
177
|
+
ensure
|
178
|
+
# TODO: this prints out [Result: OK] even if an exception is raised
|
179
|
+
result_reporter.write_report
|
180
|
+
|
181
|
+
# TODO: this is not the correct error condition.
|
182
|
+
unless shell_session.success?
|
183
|
+
run_failure << result_reporter.failure_summary
|
184
|
+
handle_host_failure_strategy hostname, result_reporter.failure_summary
|
185
|
+
end
|
186
|
+
@result_reports << result_reporter
|
187
|
+
@run_failures << run_failure unless run_failure.empty?
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
def handle_host_failure_strategy(hostname, failure_summary=nil)
|
193
|
+
failure_strategy = @loom_config.run_failure_strategy.to_sym
|
194
|
+
|
195
|
+
case failure_strategy
|
196
|
+
when :exclude_host
|
197
|
+
Loom.log.warn "disabling host per :run_failure_strategy => #{failure_strategy}"
|
198
|
+
@inventory_list.disable hostname
|
199
|
+
when :fail_fast
|
200
|
+
Loom.log.error "erroring out of failed scenario per :run_failure_strategy"
|
201
|
+
raise FailFastExecutionError, failure_summary
|
202
|
+
when :cowboy
|
203
|
+
Loom.log.warn "continuing on past failed scenario per :run_failure_strategy"
|
204
|
+
else
|
205
|
+
raise ConfigError, "unknown failure_strategy: #{failure_stratgy}"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|