choria-mcorpc-support 2.21.1 → 2.23.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 (145) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mcollective.rb +1 -2
  3. data/lib/mcollective/agent/bolt_tasks.ddl +253 -0
  4. data/lib/mcollective/agent/bolt_tasks.json +365 -0
  5. data/lib/mcollective/agent/bolt_tasks.rb +178 -0
  6. data/lib/mcollective/agent/choria_util.ddl +152 -0
  7. data/lib/mcollective/agent/choria_util.json +244 -0
  8. data/lib/mcollective/agent/rpcutil.ddl +8 -4
  9. data/lib/mcollective/agent/rpcutil.json +333 -0
  10. data/lib/mcollective/agent/scout.ddl +169 -0
  11. data/lib/mcollective/agent/scout.json +224 -0
  12. data/lib/mcollective/agents.rb +7 -6
  13. data/lib/mcollective/aggregate.rb +4 -4
  14. data/lib/mcollective/aggregate/average.rb +2 -2
  15. data/lib/mcollective/aggregate/base.rb +2 -2
  16. data/lib/mcollective/aggregate/result.rb +3 -3
  17. data/lib/mcollective/aggregate/result/collection_result.rb +2 -2
  18. data/lib/mcollective/aggregate/result/numeric_result.rb +2 -2
  19. data/lib/mcollective/aggregate/sum.rb +2 -2
  20. data/lib/mcollective/aggregate/summary.rb +3 -4
  21. data/lib/mcollective/application.rb +57 -21
  22. data/lib/mcollective/application/choria.rb +189 -0
  23. data/lib/mcollective/application/completion.rb +6 -6
  24. data/lib/mcollective/application/facts.rb +11 -11
  25. data/lib/mcollective/application/federation.rb +237 -0
  26. data/lib/mcollective/application/find.rb +4 -4
  27. data/lib/mcollective/application/help.rb +3 -3
  28. data/lib/mcollective/application/inventory.rb +3 -341
  29. data/lib/mcollective/application/ping.rb +5 -51
  30. data/lib/mcollective/application/playbook.rb +207 -0
  31. data/lib/mcollective/application/plugin.rb +106 -106
  32. data/lib/mcollective/application/rpc.rb +3 -108
  33. data/lib/mcollective/application/tasks.rb +425 -0
  34. data/lib/mcollective/applications.rb +11 -10
  35. data/lib/mcollective/audit/choria.rb +33 -0
  36. data/lib/mcollective/cache.rb +2 -4
  37. data/lib/mcollective/client.rb +11 -10
  38. data/lib/mcollective/config.rb +21 -34
  39. data/lib/mcollective/connector/base.rb +2 -1
  40. data/lib/mcollective/connector/nats.ddl +9 -0
  41. data/lib/mcollective/connector/nats.rb +450 -0
  42. data/lib/mcollective/data.rb +8 -3
  43. data/lib/mcollective/data/agent_data.rb +1 -1
  44. data/lib/mcollective/data/base.rb +6 -5
  45. data/lib/mcollective/data/bolt_task_data.ddl +90 -0
  46. data/lib/mcollective/data/bolt_task_data.rb +32 -0
  47. data/lib/mcollective/data/collective_data.rb +1 -1
  48. data/lib/mcollective/data/fact_data.rb +6 -6
  49. data/lib/mcollective/data/fstat_data.rb +2 -4
  50. data/lib/mcollective/data/result.rb +7 -2
  51. data/lib/mcollective/ddl/agentddl.rb +5 -17
  52. data/lib/mcollective/ddl/base.rb +10 -13
  53. data/lib/mcollective/discovery.rb +24 -39
  54. data/lib/mcollective/discovery/choria.ddl +11 -0
  55. data/lib/mcollective/discovery/choria.rb +223 -0
  56. data/lib/mcollective/discovery/flatfile.rb +7 -8
  57. data/lib/mcollective/discovery/mc.rb +2 -2
  58. data/lib/mcollective/discovery/stdin.rb +17 -18
  59. data/lib/mcollective/exceptions.rb +13 -0
  60. data/lib/mcollective/facts/base.rb +9 -9
  61. data/lib/mcollective/facts/yaml_facts.rb +12 -12
  62. data/lib/mcollective/generators.rb +3 -3
  63. data/lib/mcollective/generators/agent_generator.rb +3 -4
  64. data/lib/mcollective/generators/base.rb +14 -15
  65. data/lib/mcollective/generators/data_generator.rb +5 -6
  66. data/lib/mcollective/log.rb +2 -2
  67. data/lib/mcollective/logger/base.rb +3 -2
  68. data/lib/mcollective/logger/console_logger.rb +10 -10
  69. data/lib/mcollective/logger/file_logger.rb +7 -7
  70. data/lib/mcollective/logger/syslog_logger.rb +11 -15
  71. data/lib/mcollective/message.rb +8 -39
  72. data/lib/mcollective/monkey_patches.rb +2 -4
  73. data/lib/mcollective/optionparser.rb +2 -1
  74. data/lib/mcollective/pluginmanager.rb +3 -5
  75. data/lib/mcollective/pluginpackager.rb +1 -3
  76. data/lib/mcollective/pluginpackager/agent_definition.rb +3 -8
  77. data/lib/mcollective/pluginpackager/forge_packager.rb +7 -9
  78. data/lib/mcollective/pluginpackager/standard_definition.rb +1 -2
  79. data/lib/mcollective/registration/base.rb +18 -16
  80. data/lib/mcollective/rpc.rb +2 -4
  81. data/lib/mcollective/rpc/actionrunner.rb +16 -18
  82. data/lib/mcollective/rpc/agent.rb +26 -43
  83. data/lib/mcollective/rpc/audit.rb +1 -0
  84. data/lib/mcollective/rpc/client.rb +67 -85
  85. data/lib/mcollective/rpc/helpers.rb +55 -62
  86. data/lib/mcollective/rpc/progress.rb +2 -2
  87. data/lib/mcollective/rpc/reply.rb +17 -19
  88. data/lib/mcollective/rpc/request.rb +7 -5
  89. data/lib/mcollective/rpc/result.rb +6 -8
  90. data/lib/mcollective/rpc/stats.rb +49 -58
  91. data/lib/mcollective/security/base.rb +13 -56
  92. data/lib/mcollective/security/choria.rb +765 -0
  93. data/lib/mcollective/shell.rb +9 -4
  94. data/lib/mcollective/signer/base.rb +28 -0
  95. data/lib/mcollective/signer/choria.rb +185 -0
  96. data/lib/mcollective/ssl.rb +8 -6
  97. data/lib/mcollective/util.rb +58 -55
  98. data/lib/mcollective/util/bolt_support.rb +176 -0
  99. data/lib/mcollective/util/bolt_support/plan_runner.rb +167 -0
  100. data/lib/mcollective/util/bolt_support/task_result.rb +94 -0
  101. data/lib/mcollective/util/bolt_support/task_results.rb +128 -0
  102. data/lib/mcollective/util/choria.rb +946 -0
  103. data/lib/mcollective/util/indifferent_hash.rb +12 -0
  104. data/lib/mcollective/util/natswrapper.rb +242 -0
  105. data/lib/mcollective/util/playbook.rb +435 -0
  106. data/lib/mcollective/util/playbook/data_stores.rb +201 -0
  107. data/lib/mcollective/util/playbook/data_stores/base.rb +99 -0
  108. data/lib/mcollective/util/playbook/data_stores/consul_data_store.rb +88 -0
  109. data/lib/mcollective/util/playbook/data_stores/environment_data_store.rb +33 -0
  110. data/lib/mcollective/util/playbook/data_stores/etcd_data_store.rb +42 -0
  111. data/lib/mcollective/util/playbook/data_stores/file_data_store.rb +106 -0
  112. data/lib/mcollective/util/playbook/data_stores/shell_data_store.rb +103 -0
  113. data/lib/mcollective/util/playbook/inputs.rb +265 -0
  114. data/lib/mcollective/util/playbook/nodes.rb +207 -0
  115. data/lib/mcollective/util/playbook/nodes/mcollective_nodes.rb +86 -0
  116. data/lib/mcollective/util/playbook/nodes/pql_nodes.rb +40 -0
  117. data/lib/mcollective/util/playbook/nodes/shell_nodes.rb +55 -0
  118. data/lib/mcollective/util/playbook/nodes/terraform_nodes.rb +65 -0
  119. data/lib/mcollective/util/playbook/nodes/yaml_nodes.rb +47 -0
  120. data/lib/mcollective/util/playbook/playbook_logger.rb +47 -0
  121. data/lib/mcollective/util/playbook/puppet_logger.rb +51 -0
  122. data/lib/mcollective/util/playbook/report.rb +152 -0
  123. data/lib/mcollective/util/playbook/task_result.rb +55 -0
  124. data/lib/mcollective/util/playbook/tasks.rb +196 -0
  125. data/lib/mcollective/util/playbook/tasks/base.rb +45 -0
  126. data/lib/mcollective/util/playbook/tasks/graphite_event_task.rb +64 -0
  127. data/lib/mcollective/util/playbook/tasks/mcollective_task.rb +356 -0
  128. data/lib/mcollective/util/playbook/tasks/shell_task.rb +93 -0
  129. data/lib/mcollective/util/playbook/tasks/slack_task.rb +105 -0
  130. data/lib/mcollective/util/playbook/tasks/webhook_task.rb +136 -0
  131. data/lib/mcollective/util/playbook/template_util.rb +98 -0
  132. data/lib/mcollective/util/playbook/uses.rb +169 -0
  133. data/lib/mcollective/util/tasks_support.rb +751 -0
  134. data/lib/mcollective/util/tasks_support/cli.rb +260 -0
  135. data/lib/mcollective/util/tasks_support/default_formatter.rb +138 -0
  136. data/lib/mcollective/util/tasks_support/json_formatter.rb +108 -0
  137. data/lib/mcollective/validator.rb +6 -1
  138. data/lib/mcollective/validator/bolt_task_name_validator.ddl +7 -0
  139. data/lib/mcollective/validator/bolt_task_name_validator.rb +11 -0
  140. data/lib/mcollective/validator/length_validator.rb +1 -3
  141. metadata +65 -6
  142. data/lib/mcollective/application/describe_filter.rb +0 -87
  143. data/lib/mcollective/matcher.rb +0 -220
  144. data/lib/mcollective/matcher/parser.rb +0 -128
  145. data/lib/mcollective/matcher/scanner.rb +0 -241
@@ -0,0 +1,106 @@
1
+ require_relative "base"
2
+
3
+ require "json"
4
+ require "yaml"
5
+
6
+ module MCollective
7
+ module Util
8
+ class Playbook
9
+ class DataStores
10
+ class FileDataStore < Base
11
+ attr_accessor :file, :format, :create
12
+
13
+ def startup_hook
14
+ @file_mutex = Mutex.new
15
+ end
16
+
17
+ def from_hash(properties)
18
+ @file = properties["file"]
19
+ @format = properties["format"]
20
+ @create = !!properties["create"]
21
+
22
+ validate_configuration!
23
+
24
+ self
25
+ end
26
+
27
+ def validate_configuration!
28
+ raise("No file given to use as data source") unless @file
29
+ raise("No file format given") unless @format
30
+ raise("File format has to be one of 'json' or 'yaml'") unless ["json", "yaml"].include?(@format)
31
+
32
+ @file = File.expand_path(@file)
33
+
34
+ FileUtils.touch(@file) if @create && !File.exist?(@file)
35
+
36
+ raise("Cannot find data file %s" % @file) unless File.exist?(@file)
37
+ raise("Cannot read data file %s" % @file) unless File.readable?(@file)
38
+ raise("Cannot write data file %s" % @file) unless File.writable?(@file)
39
+
40
+ raise("The data file must contain a Hash or be empty") unless data.is_a?(Hash)
41
+ end
42
+
43
+ def data
44
+ @file_mutex.synchronize do
45
+ parse_data
46
+ end
47
+ end
48
+
49
+ def parse_data
50
+ return({}) if File.size(@file) == 0
51
+
52
+ case @format
53
+ when "json"
54
+ JSON.parse(File.read(@file))
55
+ when "yaml"
56
+ YAML.load(File.read(@file))
57
+ end
58
+ end
59
+
60
+ def save_data(raw_data)
61
+ File.open(@file, "w") do |f|
62
+ case @format
63
+ when "json"
64
+ f.print(JSON.dump(raw_data))
65
+ when "yaml"
66
+ f.print(YAML.dump(raw_data))
67
+ end
68
+ end
69
+ end
70
+
71
+ def read(key)
72
+ raise("No such key %s" % [key]) unless include?(key)
73
+
74
+ data[key]
75
+ end
76
+
77
+ def write(key, value)
78
+ @file_mutex.synchronize do
79
+ raw_data = parse_data
80
+
81
+ raw_data[key] = value
82
+
83
+ save_data(raw_data)
84
+ end
85
+
86
+ read(key)
87
+ end
88
+
89
+ def delete(key)
90
+ @file_mutex.synchronize do
91
+ raw_data = parse_data
92
+
93
+ raw_data.delete(key)
94
+
95
+ save_data(raw_data)
96
+ end
97
+ end
98
+
99
+ def include?(key)
100
+ data.include?(key)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,103 @@
1
+ require_relative "base"
2
+
3
+ module MCollective
4
+ module Util
5
+ class Playbook
6
+ class DataStores
7
+ class ShellDataStore < Base
8
+ attr_reader :command, :timeout, :environment, :cwd
9
+
10
+ def write(key, value)
11
+ run("write", key, "CHORIA_DATA_VALUE" => value)
12
+
13
+ nil
14
+ end
15
+
16
+ def delete(key)
17
+ run("delete", key)
18
+
19
+ nil
20
+ end
21
+
22
+ def read(key)
23
+ run("read", key).stdout.chomp
24
+ end
25
+
26
+ def run(action, key, environment={})
27
+ validate_key(key)
28
+
29
+ command = "%s --%s" % [@command, action]
30
+ options = shell_options
31
+
32
+ options["environment"].merge!(
33
+ environment.merge(
34
+ "CHORIA_DATA_KEY" => key,
35
+ "CHORIA_DATA_ACTION" => action
36
+ )
37
+ )
38
+
39
+ shell = run_command(command, options)
40
+
41
+ unless shell.status.exitstatus == 0
42
+ Log.warn("While running command %s: %s" % [command, shell.stderr])
43
+ raise("Could not %s key %s, got exitcode %d" % [action, key, shell.status.exitstatus])
44
+ end
45
+
46
+ shell
47
+ end
48
+
49
+ def run_command(command, options)
50
+ shell = Shell.new(command, options)
51
+ shell.runcommand
52
+ shell
53
+ end
54
+
55
+ def validate_key(key)
56
+ raise("Valid keys must match ^[a-zA-Z0-9_-]+$") unless key =~ /^[a-zA-Z0-9_-]+$/
57
+
58
+ true
59
+ end
60
+
61
+ def from_hash(properties)
62
+ @command = properties["command"]
63
+ @timeout = properties.fetch("timeout", 10)
64
+ @environment = properties.fetch("environment", {})
65
+ @cwd = properties["cwd"]
66
+
67
+ self
68
+ end
69
+
70
+ def validate_configuration!
71
+ raise("A command is required") unless @command
72
+ raise("Command %s is not executable" % @command) unless File.executable?(@command)
73
+ raise("Timeout should be an integer") unless @timeout.to_i.to_s == @timeout.to_s
74
+
75
+ if @environment
76
+ raise("Environment should be a hash") unless @environment.is_a?(Hash)
77
+
78
+ all_strings = @environment.map {|k, v| k.is_a?(String) && v.is_a?(String)}.all?
79
+ raise("All keys and values in the environment must be strings") unless all_strings
80
+ end
81
+
82
+ if @cwd
83
+ raise("cwd %s does not exist" % @cwd) unless File.exist?(@cwd)
84
+ raise("cwd %s is not a directory" % @cwd) unless File.directory?(@cwd)
85
+ end
86
+ end
87
+
88
+ def shell_options
89
+ unless @__options
90
+ @__options = {}
91
+ @__options["cwd"] = @cwd if @cwd
92
+ @__options["environment"] = @environment
93
+ @__options["timeout"] = Integer(@timeout)
94
+ end
95
+
96
+ # bacause environment is being edited
97
+ Marshal.load(Marshal.dump(@__options))
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,265 @@
1
+ module MCollective
2
+ module Util
3
+ class Playbook
4
+ class Inputs
5
+ def initialize(playbook)
6
+ @playbook = playbook
7
+ @inputs = {}
8
+ end
9
+
10
+ # List of known input names
11
+ #
12
+ # @return [Array<String>]
13
+ def keys
14
+ @inputs.keys
15
+ end
16
+
17
+ # List of known input names that have static values
18
+ #
19
+ # @return [Array<String>]
20
+ def static_keys
21
+ keys - dynamic_keys
22
+ end
23
+
24
+ # List of known input names that have dynamic values
25
+ #
26
+ # @return [Array<String>]
27
+ def dynamic_keys
28
+ @inputs.select do |_, props|
29
+ props[:dynamic]
30
+ end.keys
31
+ end
32
+
33
+ # Creates options for each input in the application
34
+ #
35
+ # @param application [MCollective::Application]
36
+ # @param set_required [Boolean] when true required inputs will be required on the CLI
37
+ # @raise [StandardError] for invalidly defined inputs
38
+ def add_cli_options(application, set_required)
39
+ @inputs.each do |input, props|
40
+ i_props = props[:properties]
41
+
42
+ next if i_props["dynamic"]
43
+
44
+ type = case i_props["type"]
45
+ when :string, "String"
46
+ String
47
+ when :fixnum, "Fixnum", "Integer"
48
+ Integer
49
+ when :float, "Float"
50
+ Float
51
+ when :numeric, "Numeric"
52
+ Numeric
53
+ when :array, ":array", "Array"
54
+ :array
55
+ when :bool, ":bool", ":boolean", "Boolean"
56
+ :boolean
57
+ else
58
+ raise("Invalid input type %s given for input %s" % [i_props["type"], input])
59
+ end
60
+
61
+ description = "%s (%s) %s" % [i_props["description"], type, i_props["default"] ? ("default: %s" % i_props["default"]) : ""]
62
+
63
+ option_params = {
64
+ :description => description,
65
+ :arguments => ["--%s %s" % [input.downcase, input.upcase]],
66
+ :type => type
67
+ }
68
+
69
+ option_params[:arguments] = ["--%s" % input.downcase] if type == :boolean
70
+
71
+ option_params[:required] = i_props["required"] if set_required && !i_props.include?("data")
72
+
73
+ application.class.option(input, option_params)
74
+ end
75
+ end
76
+
77
+ # Attempts to find values for all declared inputs
78
+ #
79
+ # During tests saving to the ds can be skipped by setting
80
+ # @save_during_prepare to false
81
+ #
82
+ # @param data [Hash] input data from CLI etc
83
+ # @return [void]
84
+ # @raise [StandardError] when required data could not be found
85
+ # @raise [StandardError] when validation fails for any data
86
+ def prepare(data={})
87
+ @inputs.each do |input, props|
88
+ next unless data.include?(input)
89
+ next if data[input].nil?
90
+ next if props[:properties]["dynamic"]
91
+
92
+ validate_data(input, data[input])
93
+
94
+ props[:value] = data[input]
95
+ props[:dynamic] = false
96
+ end
97
+
98
+ validate_requirements
99
+ end
100
+
101
+ # Checks if a input is dynamic
102
+ #
103
+ # Dynaic inputs are those where the data is sourced from
104
+ # a data source, an input that has a data source defined
105
+ # and had a specific input given will not be dynamic
106
+ #
107
+ # @return [Boolean]
108
+ def dynamic?(input)
109
+ @inputs[input][:dynamic]
110
+ end
111
+
112
+ def include?(input)
113
+ @inputs.include?(input)
114
+ end
115
+
116
+ # Looks up data from a datastore, returns default when not found
117
+ #
118
+ # @param input [String] input name
119
+ # @return [Object] value from the ds
120
+ # @raise [StandardError] for invalid inputs and ds errors
121
+ def lookup_from_datastore(input)
122
+ raise("Unknown input %s" % input) unless include?(input)
123
+
124
+ properties = @inputs[input][:properties]
125
+
126
+ value = @playbook.data_stores.read(properties["data"])
127
+ validate_data(input, value)
128
+
129
+ value
130
+ rescue
131
+ raise("Could not resolve %s for input %s: %s: %s" % [properties["data"], input, $!.class, $!.to_s]) unless properties.include?("default")
132
+
133
+ Log.warn("Could not find %s, returning default value" % properties["data"])
134
+
135
+ properties["default"]
136
+ end
137
+
138
+ # Saves static property values in their associated data stores for any keys with save set
139
+ def save_input_data
140
+ @inputs.each do |input, props|
141
+ next unless props[:properties]["save"]
142
+ next unless props[:properties]["data"]
143
+ next unless props[:value]
144
+ next if props[:dynamic]
145
+
146
+ save_to_datastore(input)
147
+ end
148
+ end
149
+
150
+ # Saves the value of a input to the data entry associated with it
151
+ #
152
+ # @see #save_input_data
153
+ # @param input [String] input name
154
+ # @raise [StandardError] for invalid inputs and ds errors
155
+ def save_to_datastore(input)
156
+ raise("Unknown input %s" % input) unless include?(input)
157
+
158
+ i_data = @inputs[input]
159
+
160
+ raise("Input %s has no value, cannot store it" % input) unless i_data.include?(:value)
161
+
162
+ Log.debug("Saving value for input %s to data item %s" % [input, i_data[:properties]["data"]])
163
+
164
+ @playbook.data_stores.write(i_data[:properties]["data"], i_data[:value])
165
+ end
166
+
167
+ # Retrieves the value for a specific input
168
+ #
169
+ # @param input [String] input name
170
+ # @return [Object]
171
+ # @raise [StandardError] for unknown inputs
172
+ def [](input)
173
+ raise("Unknown input %s" % input) unless include?(input)
174
+
175
+ props = @inputs[input][:properties]
176
+
177
+ if @inputs[input].include?(:value)
178
+ Log.debug("Resolving %s as static" % [input])
179
+ @inputs[input][:value]
180
+ elsif props.include?("data")
181
+ Log.debug("Resolving %s as dynamic" % [input])
182
+ lookup_from_datastore(input)
183
+ elsif props.include?("default")
184
+ Log.debug("Resolving %s as default" % [input])
185
+ props["default"]
186
+ else
187
+ raise("Input %s has no value, data source or default" % [input])
188
+ end
189
+ end
190
+
191
+ # Retrieves the properties for a specific input
192
+ #
193
+ # @param input [String] input name
194
+ # @return [Hash]
195
+ # @raise [StandardError] for unknown inputs
196
+ def input_properties(input)
197
+ if include?(input)
198
+ @inputs[input][:properties]
199
+ else
200
+ raise("Unknown input %s" % input)
201
+ end
202
+ end
203
+
204
+ # Checks all required inputs have values
205
+ #
206
+ # @raise [StandardError] when not
207
+ def validate_requirements
208
+ invalid = @inputs.map do |input, props|
209
+ next unless props[:properties]["required"]
210
+ next if props[:properties].include?("data")
211
+
212
+ unless props[:value]
213
+ Log.warn("Input %s requires a value but has none or nil" % input)
214
+ input
215
+ end
216
+ end.compact
217
+
218
+ raise("Values were required but not given for inputs: %s" % invalid.join(", ")) unless invalid.empty?
219
+ end
220
+
221
+ # Validates a piece of data against an input
222
+ #
223
+ # @todo this seems quite limited, we have to expand with real needs
224
+ # @param input [String] a valid input name
225
+ # @param value [Object] a value to validate
226
+ # @raise [StandardError] on validation failure
227
+ def validate_data(input, value)
228
+ return unless @inputs[input][:properties].include?("validation")
229
+
230
+ validator = @inputs[input][:properties]["validation"]
231
+
232
+ case validator
233
+ when /^:(.+)/
234
+ validator = $1.intern
235
+ when /^\/(.+)\/$/
236
+ validator = Regexp.new($1)
237
+ end
238
+
239
+ Log.debug("Validating input %s using %s validator" % [input, validator])
240
+
241
+ Validator.validate(value, validator)
242
+ rescue
243
+ Log.warn("Attempt to validate input %s with validator %s failed: %s" % [input, validator, $!.to_s]) if validator
244
+
245
+ raise("Failed to validate value for input %s: %s" % [input, $!.to_s])
246
+ end
247
+
248
+ def from_hash(data)
249
+ data.each do |input, props|
250
+ props["required"] = true unless props.include?("required")
251
+
252
+ Log.debug("Loading input %s" % [input])
253
+
254
+ @inputs[input] = {
255
+ :properties => props,
256
+ :dynamic => props.include?("data")
257
+ }
258
+ end
259
+
260
+ self
261
+ end
262
+ end
263
+ end
264
+ end
265
+ end