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,113 @@
|
|
1
|
+
module Loom::Mods
|
2
|
+
|
3
|
+
ModActionError = Class.new Loom::LoomError
|
4
|
+
InvalidModActionSignature = Class.new Loom::LoomError
|
5
|
+
|
6
|
+
class Module
|
7
|
+
attr_accessor :shell, :loom_config, :loom, :mods, :action_proxy
|
8
|
+
|
9
|
+
def initialize(shell, loom_config)
|
10
|
+
unless shell && shell.is_a?(Loom::Shell::Core)
|
11
|
+
raise "missing shell for mod #{self} => #{shell}"
|
12
|
+
end
|
13
|
+
unless loom_config && loom_config.is_a?(Loom::Config)
|
14
|
+
raise "missing config for mod #{self} => #{loom_config}"
|
15
|
+
end
|
16
|
+
|
17
|
+
@shell = shell
|
18
|
+
@loom = shell.shell_api
|
19
|
+
@mods = shell.mod_loader
|
20
|
+
@loom_config = loom_config
|
21
|
+
|
22
|
+
# The action proxy is a facade for the mod provided to patterns by the
|
23
|
+
# ShellApi (the 'loom' object). The ShellApi calls back to the mod_loader
|
24
|
+
# on method missing which instantiates a new Module object and returns the
|
25
|
+
# action_proxy.
|
26
|
+
@action_proxy = self.class.action_proxy self, shell.shell_api
|
27
|
+
@action_args = nil
|
28
|
+
@action_block = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def init_action(*args, &pattern_block)
|
32
|
+
@action_args = args
|
33
|
+
@action_block = pattern_block
|
34
|
+
end
|
35
|
+
|
36
|
+
def execute(*args, &pattern_block)
|
37
|
+
if respond_to? :mod_block
|
38
|
+
Loom.log.debug3(self) { "executing mod block => #{args} #{pattern_block}" }
|
39
|
+
mod_block *args, &pattern_block
|
40
|
+
else
|
41
|
+
Loom.log.debug3(self) { "initing action => #{args}" }
|
42
|
+
init_action *args, &pattern_block
|
43
|
+
action_proxy
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class << self
|
48
|
+
|
49
|
+
def register_mod(name, **opts, &block)
|
50
|
+
Loom.log.debug2(self) { "registered mod => #{name}" }
|
51
|
+
|
52
|
+
if block_given?
|
53
|
+
Loom.log.debug2(self) { "acting as mod_block => #{name}:#{block}" }
|
54
|
+
define_method :mod_block, &block
|
55
|
+
end
|
56
|
+
|
57
|
+
# TODO: Currently registering a block for a mod is different than
|
58
|
+
# importing actions because of how the mod gets executed. When actions
|
59
|
+
# are imorted, the mod is treated as an object providing access to the
|
60
|
+
# actions (via the action_proxy), the action proxy is provided to the
|
61
|
+
# calling pattern via {#execute}. When a block is registered, then the
|
62
|
+
# mod is only a sinlge method executed immediately via #{execute}. The
|
63
|
+
# method signature for the block and action proxy method are the
|
64
|
+
# same... this should be simplified.
|
65
|
+
ModLoader.register_mod self, name, **opts
|
66
|
+
end
|
67
|
+
|
68
|
+
def required_commands(*cmds)
|
69
|
+
@required_commands ||= []
|
70
|
+
@required_commands.push *cmds unless cmds.empty?
|
71
|
+
@required_commands
|
72
|
+
end
|
73
|
+
|
74
|
+
def import_actions(action_module, namespace=nil)
|
75
|
+
action_module.instance_methods.each do |action_name|
|
76
|
+
bound_method_name = bind_action(
|
77
|
+
action_name, action_module.instance_method(action_name), namespace)
|
78
|
+
|
79
|
+
action_map.add_action action_name, bound_method_name, namespace
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def bind_action(action_name, unbound_method, namespace=nil)
|
84
|
+
bound_method_name = [namespace, action_name].compact.join '_'
|
85
|
+
|
86
|
+
define_method bound_method_name do |*args, &block|
|
87
|
+
Loom.log.debug1(self) { "exec mod action #{self.class}##{bound_method_name}" }
|
88
|
+
|
89
|
+
bound_method = unbound_method.bind self
|
90
|
+
bound_method.call *args, &block
|
91
|
+
end
|
92
|
+
Loom.log.debug2(self) { "bound mod action => #{self.class.name}##{action_name}" }
|
93
|
+
|
94
|
+
bound_method_name
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# This needs more thought
|
99
|
+
def action_proxy(mod, shell_api)
|
100
|
+
@action_proxy_klasses ||= {}
|
101
|
+
@action_proxy_klasses[mod.class.hash] ||=
|
102
|
+
ActionProxy.subclass_for_action_map action_map
|
103
|
+
@action_proxy_klasses[mod.class.hash].new mod, shell_api
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
def action_map
|
108
|
+
@action_map ||= ActionProxy.new_action_map
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
data/lib/loom/pattern.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
module Loom::Pattern
|
2
|
+
|
3
|
+
# DO NOT ADD METHODS TO THIS MODULE!!!
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def included(pattern_mod)
|
7
|
+
Loom.log.debug2(self) { "pattern module loaded => #{pattern_mod}" }
|
8
|
+
pattern_mod.extend DSL
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# DO NOT ADD METHODS TO THIS MODULE!!!
|
13
|
+
end
|
14
|
+
|
15
|
+
require_relative "pattern/all"
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Loom::Pattern
|
2
|
+
|
3
|
+
##
|
4
|
+
# Pattern::DefinitionContext is the collection of facts, hooks, and
|
5
|
+
# parent contexts a pattern is defined along side of. The context
|
6
|
+
# includes all contexts of parent modules.
|
7
|
+
class DefinitionContext
|
8
|
+
|
9
|
+
def initialize(pattern_module, parent_context=nil)
|
10
|
+
@fact_map = pattern_module.facts.dup
|
11
|
+
@let_map = pattern_module.let_map.dup
|
12
|
+
|
13
|
+
@hooks = pattern_module.hooks.dup
|
14
|
+
@parent_context = parent_context
|
15
|
+
|
16
|
+
@merged_fact_map = merged_fact_map
|
17
|
+
@merged_let_map = merged_let_map
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :let_map, :fact_map, :hooks
|
21
|
+
|
22
|
+
##
|
23
|
+
# Merges the facts defined by the pattern context with the host
|
24
|
+
# fact_set
|
25
|
+
def fact_set(host_fact_set)
|
26
|
+
host_fact_set.merge merged_fact_map
|
27
|
+
end
|
28
|
+
|
29
|
+
# TODO: #define_let_readers is a TERRIBLE name. Rename this method.
|
30
|
+
#
|
31
|
+
# The method is called by Reference#call with the Reference::RunContext
|
32
|
+
# instance. The "let" defined local declarations are added as method
|
33
|
+
# definitions on each RunContext instance. The @merged_let_map is the map of
|
34
|
+
# let definitions, merged from the associated module, up the namespace
|
35
|
+
# graph, allowing for inheriting and overriding +let+ declarations.
|
36
|
+
def define_let_readers(scope_object, fact_set)
|
37
|
+
@merged_let_map.each do |let_key, block|
|
38
|
+
raise "no let block" unless block
|
39
|
+
value = scope_object.instance_exec fact_set, &block
|
40
|
+
scope_object.define_singleton_method(let_key) { value }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def before_hooks
|
45
|
+
Hook.before_hooks merged_hooks
|
46
|
+
end
|
47
|
+
|
48
|
+
def after_hooks
|
49
|
+
Hook.after_hooks merged_hooks.reverse
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def merged_fact_map
|
54
|
+
merged_contexts.map(&:fact_map).reduce({}) do |merged_map, next_map|
|
55
|
+
merged_map.merge! next_map
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def merged_let_map
|
60
|
+
merged_contexts.map(&:let_map).reduce({}) do |merged_map, next_map|
|
61
|
+
merged_map.merge! next_map
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def merged_hooks
|
66
|
+
return hooks if @parent_context.nil?
|
67
|
+
merged_contexts.map(&:hooks).flatten
|
68
|
+
end
|
69
|
+
|
70
|
+
def merged_contexts
|
71
|
+
[@parent_context, self].flatten.compact
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
# .loom File DSL
|
4
|
+
|
5
|
+
Loom::Pattern::DSL is the mixin that defines the declarative API for all .loom
|
6
|
+
file defined modules. It is included into Loom::Pattern by default. The outer
|
7
|
+
most module that a .loom file declares has Loom::Pattern mixed in by
|
8
|
+
default. Submodules must explicitly include Loom::Pattern, and will receive DSL.
|
9
|
+
|
10
|
+
To follow the code path for .loom file loading see:
|
11
|
+
|
12
|
+
Loom::Runner#load
|
13
|
+
-> Loom::Pattern::Loader.load
|
14
|
+
-> Loom::Pattern::ReferenceSet.load_from_file
|
15
|
+
-> Loom::Pattern::ReferenceSet::Builder.create
|
16
|
+
|
17
|
+
The Loom::Pattern::ReferenceSet::Builder creates a ReferenceSet from a .loom
|
18
|
+
file. A ReferenseSet being a collection of references with uniquely named
|
19
|
+
slugs. The slug of a reference is computed from the module namespace and
|
20
|
+
instance method name. For example, given the following .loom file:
|
21
|
+
|
22
|
+
``` ~ruby
|
23
|
+
def top_level; end
|
24
|
+
|
25
|
+
module Outer
|
26
|
+
|
27
|
+
desc 'The first pattern'
|
28
|
+
pattern :first do |loom, facts|; end
|
29
|
+
|
30
|
+
module Inner
|
31
|
+
desc 'The second pattern'
|
32
|
+
pattern :second do |loom, facts|; end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
It declares a reference set with slugs:
|
38
|
+
|
39
|
+
* top_level
|
40
|
+
* outer:first
|
41
|
+
* outer:inner:second
|
42
|
+
|
43
|
+
Defining the same pattern slug twice raises a DuplicatPatternRef error.
|
44
|
+
|
45
|
+
Module Inner inherits all +let+ declarations from its outer contexts, both ::
|
46
|
+
(root) and ::Outer. +before+ hooks are run from a top-down module ordering,
|
47
|
+
+after+ hooks are run bottom-up. For example, given the following .loom file:
|
48
|
+
|
49
|
+
``` ~ruby
|
50
|
+
let(:var_1) { "v1 value" }
|
51
|
+
let(:var_2) { "v2 value" }
|
52
|
+
|
53
|
+
before { puts "runs first +before+" }
|
54
|
+
after { puts "runs last +after+" }
|
55
|
+
|
56
|
+
def top_level; end
|
57
|
+
|
58
|
+
module Submod
|
59
|
+
let(:var_1) { "submod value" }
|
60
|
+
|
61
|
+
before { puts "runs second +before+" }
|
62
|
+
after { puts "runs first +after+" }
|
63
|
+
|
64
|
+
pattern :a_pattern { |loom, facts}
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
68
|
+
|
69
|
+
If running `loom submod:a_pattern`, then let declarations would declare values:
|
70
|
+
|
71
|
+
{ :var_1 => "submod value", :var_2 => "v2 value" }
|
72
|
+
|
73
|
+
Each let value is effectively available as an `attr_reader` declaration from
|
74
|
+
::Submod#a_pattern. Before and After hook ordering with pattern execution would
|
75
|
+
look like:
|
76
|
+
|
77
|
+
=> runs first +before+
|
78
|
+
=> runs second +before+
|
79
|
+
=> `submod:a_pattern`
|
80
|
+
=> runs first +after+
|
81
|
+
=> runs last +after+
|
82
|
+
|
83
|
+
For the code that executes before hooks, pattern, after hooks see
|
84
|
+
Loom::Pattern::Reference::RunContext#run.
|
85
|
+
|
86
|
+
The Loom::Pattern::Reference::RunContext acts as the the binding object for each
|
87
|
+
pattern slug. i.e. When running a pattern slug, the RunContext is the self
|
88
|
+
object. Let definitions, before and after hooks, and fact maps are unique to
|
89
|
+
each RunContext, for each RunContext they are defined in the
|
90
|
+
Loom::Pattern::DefinitionContext. Each DefinitionContext is merged from it's
|
91
|
+
parent module, see Loom::Pattern::DefinitionContext#merge_contexts for info.
|
92
|
+
|
93
|
+
The RunContext#run method is the actual execution of the pattern. A pattern,
|
94
|
+
before association to a RunContext instance is an unbound method. During
|
95
|
+
RunContext#initialize the pattern is bound to the RunContext instance and
|
96
|
+
executed during RunContext#run with the associated Loom::Shell::Api and
|
97
|
+
Loom::Facts::FactSet as parameters.
|
98
|
+
|
99
|
+
=end
|
100
|
+
module Loom::Pattern
|
101
|
+
module DSL
|
102
|
+
|
103
|
+
loom_accessor :namespace
|
104
|
+
|
105
|
+
def description(description)
|
106
|
+
@next_description = description
|
107
|
+
end
|
108
|
+
alias_method :desc, :description
|
109
|
+
|
110
|
+
def with_facts(**new_facts, &block)
|
111
|
+
@facts ||= {}
|
112
|
+
@facts.merge! new_facts
|
113
|
+
yield_result = yield @facts if block_given?
|
114
|
+
@facts = yield_result if yield_result.is_a? Hash
|
115
|
+
end
|
116
|
+
|
117
|
+
def let(name, &block)
|
118
|
+
@let_map ||= {}
|
119
|
+
@let_map[name.to_sym] = block
|
120
|
+
end
|
121
|
+
|
122
|
+
def pattern(name, &block)
|
123
|
+
Loom.log.debug3(self) { "defined pattern method => #{name}" }
|
124
|
+
@pattern_methods ||= []
|
125
|
+
@pattern_method_map ||= {}
|
126
|
+
@pattern_descriptions ||= {}
|
127
|
+
|
128
|
+
method_name = name.to_sym
|
129
|
+
|
130
|
+
@pattern_methods << method_name
|
131
|
+
@pattern_method_map[method_name] = true
|
132
|
+
@pattern_descriptions[method_name] = @next_description
|
133
|
+
@next_description = nil
|
134
|
+
|
135
|
+
define_method method_name, &block
|
136
|
+
end
|
137
|
+
|
138
|
+
def hook(scope, &block)
|
139
|
+
@hooks ||= []
|
140
|
+
@hooks << Hook.new(scope, &block)
|
141
|
+
end
|
142
|
+
|
143
|
+
def before(&block)
|
144
|
+
hook :before, &block
|
145
|
+
end
|
146
|
+
|
147
|
+
def after(&block)
|
148
|
+
hook :after, &block
|
149
|
+
end
|
150
|
+
|
151
|
+
def pattern_methods
|
152
|
+
@pattern_methods || []
|
153
|
+
end
|
154
|
+
|
155
|
+
def pattern_description(name)
|
156
|
+
@pattern_descriptions[name]
|
157
|
+
end
|
158
|
+
|
159
|
+
def pattern_method(name)
|
160
|
+
raise UnknownPatternMethod, name unless @pattern_method_map[name]
|
161
|
+
instance_method name
|
162
|
+
end
|
163
|
+
|
164
|
+
def hooks
|
165
|
+
@hooks || []
|
166
|
+
end
|
167
|
+
|
168
|
+
def facts
|
169
|
+
@facts || {}
|
170
|
+
end
|
171
|
+
|
172
|
+
def let_map
|
173
|
+
@let_map || {}
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Loom::Pattern
|
2
|
+
class Hook
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def around_hooks(hooks)
|
6
|
+
hooks.find_all { |h| h.scope == :around }
|
7
|
+
end
|
8
|
+
|
9
|
+
def before_hooks(hooks)
|
10
|
+
hooks.find_all { |h| h.scope == :before }
|
11
|
+
end
|
12
|
+
|
13
|
+
def after_hooks(hooks)
|
14
|
+
hooks.find_all { |h| h.scope == :after }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(scope, &block)
|
19
|
+
unless [:before, :around, :after].include? scope
|
20
|
+
raise 'invalid Pattern::DSL hook scope'
|
21
|
+
end
|
22
|
+
@scope = scope
|
23
|
+
@block = block
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :scope, :block
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Loom::Pattern
|
2
|
+
|
3
|
+
SiteFileNotFound = Class.new Loom::LoomError
|
4
|
+
|
5
|
+
class Loader
|
6
|
+
class << self
|
7
|
+
def load(config)
|
8
|
+
loader = Loader.new config.files.loom_files
|
9
|
+
loader.load_patterns
|
10
|
+
loader
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(pattern_files)
|
15
|
+
@loom_pattern_files = pattern_files
|
16
|
+
@reference_set = ReferenceSet.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def slugs
|
20
|
+
@reference_set.slugs
|
21
|
+
end
|
22
|
+
|
23
|
+
def patterns(slugs=nil)
|
24
|
+
if slugs.nil?
|
25
|
+
@reference_set.pattern_refs
|
26
|
+
else
|
27
|
+
slugs.map { |slug| get_pattern_ref(slug) }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_pattern_ref(slug)
|
32
|
+
@reference_set[slug]
|
33
|
+
end
|
34
|
+
alias_method :[], :get_pattern_ref
|
35
|
+
|
36
|
+
def load_patterns
|
37
|
+
@loom_pattern_files.each do |f|
|
38
|
+
raise SiteFileNotFound, f unless File.exists? f
|
39
|
+
load_pattern_file f
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def load_pattern_file(f)
|
45
|
+
@reference_set.merge! ReferenceSet.load_from_file(f)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|