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.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.lock +99 -0
  6. data/Guardfile +54 -0
  7. data/Rakefile +6 -0
  8. data/bin/loom +185 -0
  9. data/lib/env/development.rb +1 -0
  10. data/lib/loom.rb +44 -0
  11. data/lib/loom/all.rb +20 -0
  12. data/lib/loom/config.rb +106 -0
  13. data/lib/loom/core_ext.rb +37 -0
  14. data/lib/loom/dsl.rb +60 -0
  15. data/lib/loom/facts.rb +13 -0
  16. data/lib/loom/facts/all.rb +2 -0
  17. data/lib/loom/facts/fact_file_provider.rb +86 -0
  18. data/lib/loom/facts/fact_set.rb +138 -0
  19. data/lib/loom/host_spec.rb +32 -0
  20. data/lib/loom/inventory.rb +124 -0
  21. data/lib/loom/logger.rb +141 -0
  22. data/lib/loom/method_signature.rb +174 -0
  23. data/lib/loom/mods.rb +4 -0
  24. data/lib/loom/mods/action_proxy.rb +105 -0
  25. data/lib/loom/mods/all.rb +3 -0
  26. data/lib/loom/mods/mod_loader.rb +80 -0
  27. data/lib/loom/mods/module.rb +113 -0
  28. data/lib/loom/pattern.rb +15 -0
  29. data/lib/loom/pattern/all.rb +7 -0
  30. data/lib/loom/pattern/definition_context.rb +74 -0
  31. data/lib/loom/pattern/dsl.rb +176 -0
  32. data/lib/loom/pattern/hook.rb +28 -0
  33. data/lib/loom/pattern/loader.rb +48 -0
  34. data/lib/loom/pattern/reference.rb +71 -0
  35. data/lib/loom/pattern/reference_set.rb +169 -0
  36. data/lib/loom/pattern/result_reporter.rb +77 -0
  37. data/lib/loom/runner.rb +209 -0
  38. data/lib/loom/shell.rb +12 -0
  39. data/lib/loom/shell/all.rb +10 -0
  40. data/lib/loom/shell/api.rb +48 -0
  41. data/lib/loom/shell/cmd_result.rb +33 -0
  42. data/lib/loom/shell/cmd_wrapper.rb +164 -0
  43. data/lib/loom/shell/core.rb +226 -0
  44. data/lib/loom/shell/harness_blob.rb +26 -0
  45. data/lib/loom/shell/harness_command_builder.rb +50 -0
  46. data/lib/loom/shell/session.rb +25 -0
  47. data/lib/loom/trap.rb +44 -0
  48. data/lib/loom/version.rb +3 -0
  49. data/lib/loomext/all.rb +4 -0
  50. data/lib/loomext/corefacts.rb +6 -0
  51. data/lib/loomext/corefacts/all.rb +8 -0
  52. data/lib/loomext/corefacts/facter_provider.rb +24 -0
  53. data/lib/loomext/coremods.rb +5 -0
  54. data/lib/loomext/coremods/all.rb +13 -0
  55. data/lib/loomext/coremods/exec.rb +50 -0
  56. data/lib/loomext/coremods/files.rb +104 -0
  57. data/lib/loomext/coremods/net.rb +33 -0
  58. data/lib/loomext/coremods/package/adapter.rb +100 -0
  59. data/lib/loomext/coremods/package/package.rb +62 -0
  60. data/lib/loomext/coremods/user.rb +82 -0
  61. data/lib/loomext/coremods/vm.rb +0 -0
  62. data/lib/loomext/coremods/vm/all.rb +6 -0
  63. data/lib/loomext/coremods/vm/vbox.rb +84 -0
  64. data/loom.gemspec +39 -0
  65. data/loom/inventory.yml +13 -0
  66. data/scripts/harness.sh +242 -0
  67. data/spec/loom/host_spec_spec.rb +101 -0
  68. data/spec/loom/inventory_spec.rb +154 -0
  69. data/spec/loom/method_signature_spec.rb +275 -0
  70. data/spec/loom/pattern/dsl_spec.rb +207 -0
  71. data/spec/loom/shell/cmd_wrapper_spec.rb +239 -0
  72. data/spec/loom/shell/harness_blob_spec.rb +42 -0
  73. data/spec/loom/shell/harness_command_builder_spec.rb +36 -0
  74. data/spec/runloom.sh +35 -0
  75. data/spec/scripts/harness_spec.rb +385 -0
  76. data/spec/spec_helper.rb +94 -0
  77. data/spec/test.loom +370 -0
  78. data/spec/test_loom_spec.rb +57 -0
  79. 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
@@ -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,7 @@
1
+ require_relative "dsl"
2
+ require_relative "hook"
3
+ require_relative "definition_context"
4
+ require_relative "reference"
5
+ require_relative "reference_set"
6
+ require_relative "loader"
7
+ require_relative "result_reporter"
@@ -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