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,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