loom-core 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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