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,141 @@
1
+ require 'logger'
2
+
3
+ module Loom
4
+ class Logger
5
+ COLOR_MAP = {
6
+ :d => :green,
7
+ :i => :blue,
8
+ :w => :yellow,
9
+ :e => :red,
10
+ :f => :pink
11
+ }
12
+
13
+ class << self
14
+ def configure(config)
15
+ device = configure_device config.log_device
16
+
17
+ logger = ::Logger.new device
18
+ class << logger
19
+ include LoggerDebugLevels
20
+ end
21
+ raise "missing logger debug methods" unless logger.respond_to? :debug1
22
+
23
+ logger.level = case config.log_level
24
+ when Symbol, String
25
+ ::Logger.const_get config.log_level.to_s.upcase
26
+ when Integer
27
+ # Negative numbers can be used for detailed debug levels
28
+ config.log_level
29
+ end
30
+
31
+ colorize = config.log_colorize && device.tty?
32
+ logger.formatter = default_formatter colorize
33
+
34
+ if config.log_colorize && !device.tty?
35
+ logger.warn "disabled log colorization for non-tty"
36
+ end
37
+
38
+ logger
39
+ end
40
+
41
+ private
42
+ def configure_device(device_value)
43
+ case device_value
44
+ when :stderr, 'stderr'
45
+ STDERR
46
+ when :stdout, 'stdout'
47
+ STDOUT
48
+ when :devnull, 'devnull'
49
+ File.new '/dev/null', 'a'
50
+ when Integer
51
+ IO.new device_value, 'a'
52
+ when String
53
+ File.new device_value, 'a'
54
+ when StringIO
55
+ device_value
56
+ else
57
+ raise ConfigError, "log_device => #{device_value}"
58
+ end
59
+ end
60
+
61
+ def default_formatter(colorize)
62
+ lambda do |severity, datetime, progname, msg|
63
+ s_key = severity[0].downcase.to_sym
64
+ if colorize && COLOR_MAP[s_key]
65
+ severity = Styleizer.apply severity, COLOR_MAP[s_key]
66
+ progname = Styleizer.apply progname, :bold, :dark_gray
67
+ end
68
+
69
+ if progname
70
+ "[%s] (%s): %s\n" % [severity, progname, msg]
71
+ else
72
+ "[%s] %s\n" % [severity, msg]
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ module LoggerDebugLevels
79
+ NUM_DEBUG_LEVELS = 6
80
+
81
+ ##
82
+ # Adds methods debug1, debug2, ... debug6 for more detailed
83
+ # debug levels. Set a negative index +logger.level+ to enable
84
+ # lower levels, e.g. logger.level = -6 for debug6 messages.
85
+ (1..NUM_DEBUG_LEVELS).to_a.each do |debug_level|
86
+ debug_method_name = "debug#{debug_level}"
87
+
88
+ define_method debug_method_name do |*args, &block|
89
+ severity = debug_level * -1
90
+ return if severity < self.level
91
+
92
+ debug_at_level severity, *args, &block
93
+ end
94
+ end
95
+
96
+ def format_severity(severity)
97
+ if severity < ::Logger::DEBUG
98
+ return "D" + severity.abs.to_s
99
+ else
100
+ super(severity)[0]
101
+ end
102
+ end
103
+
104
+ private
105
+ def debug_at_level(severity, progname=nil, &block)
106
+ raise 'block required for super debug loggers' unless block_given?
107
+ raise 'progname required for super debug loggers' unless progname
108
+
109
+ add severity, nil, progname, &block
110
+ end
111
+ end
112
+
113
+ class Styleizer
114
+ class << self
115
+
116
+ STYLE_CODES = {
117
+ :bold => 1,
118
+ :red => 31,
119
+ :green => 32,
120
+ :yellow => 33,
121
+ :blue => 34,
122
+ :pink => 35,
123
+ :light_blue => 36,
124
+ :light_gray => 37,
125
+ :dark_gray => 90
126
+ }
127
+
128
+ def apply(str, *styles)
129
+ return str unless str
130
+ styles.reduce(str) { |str, style| styleize STYLE_CODES[style], str }
131
+ end
132
+
133
+ private
134
+ def styleize(color_code, str)
135
+ "\e[#{color_code}m#{str}\e[0m"
136
+ end
137
+ end
138
+ end
139
+
140
+ end
141
+ end
@@ -0,0 +1,174 @@
1
+ module Loom
2
+
3
+ # Used to analyze the arguments of a method.
4
+ class MethodSignature
5
+
6
+ module ParamType
7
+ REQ = :req
8
+ OPT = :opt
9
+ REST = :rest
10
+ KEYREQ = :keyreq
11
+ KEY = :key
12
+ KEYREST = :keyrest
13
+ BLOCK = :block
14
+ end
15
+
16
+ # @param proc_or_method [#parameters] A {Proc} or {Method}
17
+ def initialize(proc_or_method)
18
+ @parameter_list = proc_or_method.parameters
19
+ @req_args = find_by_type ParamType::REQ
20
+ @opt_args = find_by_type ParamType::OPT
21
+ @rest_args = find_by_type ParamType::REST
22
+ @keyreq_args = find_by_type ParamType::KEYREQ
23
+ @key_args = find_by_type ParamType::KEY
24
+ @keyrest_args = find_by_type ParamType::KEYREST
25
+ @block_args = find_by_type ParamType::BLOCK
26
+ end
27
+
28
+ attr_reader :req_args, :opt_args, :rest_args, :keyreq_args, :key_args,
29
+ :keyrest_args, :block_args
30
+
31
+ def find_by_type(type)
32
+ @parameter_list.find_all { |tuple| tuple.first == type }
33
+ end
34
+
35
+ # Defines has_xyz_args? methods for each {ParamType}.
36
+ def method_missing(name, *args)
37
+ match_data = name.to_s.match /^has_([^?]+)_args\?$/
38
+ if match_data
39
+ method = "%s_args" % [match_data[1]]
40
+ !self.send(method.to_sym).empty?
41
+ else
42
+ super name, *args
43
+ end
44
+ end
45
+
46
+ class MatchSpec
47
+
48
+ class Builder
49
+ def initialize
50
+ @map = {
51
+ :req_args => 0,
52
+ :opt_args => 0,
53
+ :has_rest_args => false,
54
+ :keyreq_args => 0,
55
+ :key_args => 0,
56
+ :has_keyrest_args => false,
57
+ :has_block => false
58
+ }
59
+ end
60
+
61
+ def method_missing(name, value, *args)
62
+ @map[name.to_sym] = value
63
+ self
64
+ end
65
+
66
+ def build
67
+ MatchSpec.new(@map || {})
68
+ end
69
+ end
70
+
71
+ class << self
72
+ def builder
73
+ Builder.new
74
+ end
75
+ end
76
+
77
+ # @param req_args [Fixnum] Number of required args, nil for any.
78
+ # @param opt_args [Fixnum] Number of optional args, nil for any.
79
+ # @param has_rest_args [Boolean] Whether a *args is defined, nil
80
+ # for any. If +has_rest_args+ is true then any number of req or opt
81
+ # args will satisfy this match.
82
+ # @param keyreq_args [Fixnum] Number of required keyward args, nil
83
+ # for any.
84
+ # @param key_args [Fixnum] Number of optional keyward args, nil
85
+ # for any.
86
+ # @param has_keyrest_args [Boolean] Whether a **opts is defined,
87
+ # nil for any. If +has_keyrest_args+ is true, then any number of
88
+ # keyreq or key args will satisfy this match for name named opts.
89
+ # @param has_block [Boolean] Whether a block is defined, nil for any.
90
+ def initialize(
91
+ req_args: nil,
92
+ opt_args: nil,
93
+ has_rest_args: nil,
94
+ keyreq_args: nil,
95
+ key_args: nil,
96
+ has_keyrest_args: nil,
97
+ has_block: nil)
98
+ @req_args = req_args
99
+ @opt_args = opt_args
100
+ @has_rest_args = has_rest_args
101
+ @keyreq_args = keyreq_args
102
+ @key_args = key_args
103
+ @has_keyrest_args = has_keyrest_args
104
+ @has_block = has_block
105
+ end
106
+
107
+ # @return [Boolean]
108
+ def match?(method)
109
+ method_sig = MethodSignature.new method
110
+
111
+ # *args definition matches any call.
112
+ return true if @has_rest_args
113
+
114
+ check_ordered_args(method_sig) &&
115
+ check_keyword_args(method_sig) &&
116
+ check_block_args(method_sig)
117
+ end
118
+
119
+ private
120
+ def check_ordered_args(method_sig)
121
+ rest = check_rest(method_sig)
122
+ if rest && method_sig.has_rest_args?
123
+ Loom.log.debug1(self) { "returning from failed addon look"}
124
+ return true
125
+ end
126
+
127
+ return rest &&
128
+ check_req_args(method_sig) &&
129
+ check_opt_args(method_sig);
130
+ end
131
+
132
+ def check_rest(method_sig)
133
+ @has_rest_args.nil? || method_sig.has_rest_args? == @has_rest_args
134
+ end
135
+
136
+ def check_req_args(method_sig)
137
+ @req_args.nil? || @req_args == method_sig.req_args.size
138
+ end
139
+
140
+ def check_opt_args(method_sig)
141
+ @opt_args.nil? || @opt_args == method_sig.opt_args.size
142
+ end
143
+
144
+ def check_keyword_args(method_sig)
145
+ return true if @has_keyrest_args
146
+
147
+ return check_keyrest(method_sig) &&
148
+ check_keyreq_args(method_sig) &&
149
+ check_key_args(method_sig);
150
+ end
151
+
152
+ def check_keyrest(method_sig)
153
+ @has_keyrest_args.nil? || method_sig.has_keyrest_args? == @has_keyrest_args
154
+ end
155
+
156
+ def check_keyreq_args(method_sig)
157
+ @keyreq_args.nil? ||
158
+ @keyreq_args == method_sig.keyreq_args.size ||
159
+ method_sig.has_keyrest_args?
160
+ end
161
+
162
+ def check_key_args(method_sig)
163
+ @key_args.nil? ||
164
+ @key_args == method_sig.key_args.size ||
165
+ method_sig.has_keyrest_args?
166
+ end
167
+
168
+ def check_block_args(method_sig)
169
+ return true if @has_block.nil?
170
+ return method_sig.has_block_args? == @has_block
171
+ end
172
+ end
173
+ end
174
+ end
data/lib/loom/mods.rb ADDED
@@ -0,0 +1,4 @@
1
+ module Loom::Mods
2
+ end
3
+
4
+ require 'loom/mods/all'
@@ -0,0 +1,105 @@
1
+ # TODO: The method names in this file are atrocious and confusing, need to
2
+ # rethink these names and simplify this class.
3
+ module Loom::Mods
4
+ class ActionProxy
5
+
6
+ def initialize(mod, shell_api)
7
+ @mod = mod
8
+ @shell_api = shell_api
9
+ @nested_action_proxies = {}
10
+ end
11
+
12
+ def proxy_for_namespace(ns=nil)
13
+ ns.nil? ? self : @nested_action_proxies[ns]
14
+ end
15
+
16
+ private
17
+
18
+ class << self
19
+ def new_action_map
20
+ ActionMap.new
21
+ end
22
+
23
+ def subclass_for_action_map(action_map)
24
+ sub_class = Class.new ActionProxy
25
+ sub_class.install_action_map action_map
26
+ sub_class
27
+ end
28
+
29
+ def install_action_map(action_map)
30
+ install_root_actions action_map
31
+ install_namespace_action_proxies action_map
32
+ end
33
+
34
+ def install_root_actions(action_map)
35
+ action_map.action_tuples.each do |tuple|
36
+ public_action_name = tuple[0]
37
+ # TODO: What I've done here w/ bound_action_name (remapping methods
38
+ # from a given name to a flattened namespace on the Mod object) is
39
+ # very very strange. Just storing/binding/calling a Proc would be more
40
+ # idiomatic.
41
+ bound_action_name = tuple[1]
42
+
43
+ define_method public_action_name do |*args, &block|
44
+ # TODO: Effectively this is the API for all mods, but it's burried
45
+ # here in the middle of nowhere. Add documentation - or make it
46
+ # easier to read.
47
+ Loom.log.debug2(self) do
48
+ "proxy to mod #{@mod} => #{public_action_name}: #{args} #{block}"
49
+ end
50
+
51
+ @mod.send bound_action_name, *args, &block
52
+ end
53
+ Loom.log.debug2 self do
54
+ "defined action proxy action: #{public_action_name} => #{bound_action_name}"
55
+ end
56
+ end
57
+ end
58
+
59
+ ##
60
+ # This gets a bit tricky
61
+ def install_namespace_action_proxies(action_map)
62
+ action_map.ns_actionmaps.each do |ns, ns_action_map|
63
+ @nested_action_proxy_klasses ||= {}
64
+ @nested_action_proxy_klasses[self.hash] ||= {}
65
+ @nested_action_proxy_klasses[self.hash][ns] ||=
66
+ ActionProxy.subclass_for_action_map ns_action_map
67
+ action_proxy_klass = @nested_action_proxy_klasses[self.hash][ns]
68
+
69
+ define_method ns do
70
+ @nested_action_proxies[ns] ||= action_proxy_klass.new @mod
71
+ end
72
+ Loom.log.debug2 self do
73
+ "defined action proxy ns: #{ns}"
74
+ end
75
+ end
76
+ end
77
+
78
+ end
79
+
80
+ class ActionMap
81
+
82
+ attr_reader :action_tuples, :ns_actionmaps
83
+
84
+ def initialize
85
+ @action_tuples = []
86
+ @ns_actionmaps = {}
87
+ end
88
+
89
+ def add_action(action_name, bound_method_name, namespace=nil)
90
+ if namespace.nil?
91
+ tuple = [action_name, bound_method_name]
92
+ @action_tuples << tuple unless namespace
93
+ else
94
+ # Adds an action name to a nested ActionMap
95
+ add_namespace(namespace).add_action action_name, bound_method_name
96
+ end
97
+ end
98
+
99
+ private
100
+ def add_namespace(ns)
101
+ @ns_actionmaps[ns] ||= ActionMap.new
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,3 @@
1
+ require_relative "mod_loader"
2
+ require_relative "action_proxy"
3
+ require_relative "module"
@@ -0,0 +1,80 @@
1
+ module Loom::Mods
2
+
3
+ AliasRegisteredError = Class.new Loom::LoomError
4
+ AnonymousModLoadError = Class.new Loom::LoomError
5
+ ModDefinedError = Class.new Loom:: LoomError
6
+ ModNotRegisteredError = Class.new Loom::LoomError
7
+
8
+ class ModLoader
9
+ def initialize(loom_config)
10
+ @loom_config = loom_config
11
+ end
12
+
13
+ def verify_shell_cmds(shell, mod_klass)
14
+ Loom.log.debug2(self) { "verifying cmds for mod => #{mod_klass}" }
15
+ mod_klass.required_commands.each do |cmd|
16
+ begin
17
+ shell.verify_which cmd
18
+ rescue Loom::Shell::VerifyError
19
+ Loom.log.error "unable to use mod #{mod_klass}, missing required command => #{cmd}"
20
+ raise $!
21
+ end
22
+ end
23
+ end
24
+
25
+ class << self
26
+
27
+ def register_mod(klass, name, **opts)
28
+ name = name.to_sym
29
+ raise AnonymousModLoadError, 'cannot load anonymous mods' unless name
30
+ raise ModDefinedError, name if instance_methods.include? name
31
+
32
+ define_mod_factory name, klass
33
+ Loom.log.debug1(self) { "registered mod => #{klass} as #{name}" }
34
+
35
+ opts.each do |k,v|
36
+ case k
37
+ when :alias
38
+ [v].flatten.each { |v| alias_module klass, v }
39
+ else
40
+ raise "unknown option #{k}"
41
+ end
42
+ end
43
+ end
44
+
45
+ # TODO: add some documentation here, this is the entrypoint for all mod
46
+ # factories and returning the ActionProxy or running a ModBlock. This is
47
+ # just as hidden as ActionProxy+install_root_actions+
48
+ def define_mod_factory(name, mod_klass)
49
+ raise ModDefinedError, name if instance_methods.include? name
50
+ registered_mods[mod_klass.name] = [name]
51
+
52
+ define_method name do |shell, *args, &pattern_block|
53
+ Loom.log.debug3(self) do
54
+ "handling mod call => #{mod_klass}##{name} #{args} #{pattern_block}"
55
+ end
56
+ verify_shell_cmds shell, mod_klass
57
+
58
+ mod = mod_klass.new shell, @loom_config
59
+ mod.execute *args, &pattern_block
60
+ end
61
+ end
62
+
63
+ def registered_mods
64
+ @registered_mods ||= {}
65
+ end
66
+
67
+ private
68
+ def alias_module(klass, alias_name)
69
+ raise ModNotRegisteredError, klass unless registered_mods[klass.name]
70
+ raise AliasRegisteredError, alias_name if instance_methods.include? alias_name
71
+
72
+ original_method = registered_mods[klass.name].first
73
+ registered_mods[klass.name] << alias_name
74
+
75
+ alias_method alias_name.to_sym, original_method
76
+ Loom.log.debug1(self) { "mod aliased => #{original_method} as #{alias_name}" }
77
+ end
78
+ end
79
+ end
80
+ end