loom-core 0.0.1
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.
- 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
|