jerakia 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hiera/backend/jerakia_backend.rb +13 -14
  3. data/lib/jerakia/answer.rb +28 -27
  4. data/lib/jerakia/cache/entry.rb +2 -6
  5. data/lib/jerakia/cache/file.rb +53 -23
  6. data/lib/jerakia/cache.rb +44 -11
  7. data/lib/jerakia/cli/lookup.rb +124 -0
  8. data/lib/jerakia/cli/server.rb +50 -0
  9. data/lib/jerakia/cli/token.rb +64 -0
  10. data/lib/jerakia/cli.rb +7 -117
  11. data/lib/jerakia/config.rb +5 -5
  12. data/lib/jerakia/datasource/dummy.rb +1 -6
  13. data/lib/jerakia/datasource/file/json.rb +1 -3
  14. data/lib/jerakia/datasource/file/yaml.rb +1 -3
  15. data/lib/jerakia/datasource/file.rb +21 -44
  16. data/lib/jerakia/datasource/http.rb +17 -23
  17. data/lib/jerakia/datasource.rb +37 -36
  18. data/lib/jerakia/dsl/lookup.rb +4 -6
  19. data/lib/jerakia/dsl/policy.rb +11 -12
  20. data/lib/jerakia/error.rb +0 -5
  21. data/lib/jerakia/launcher.rb +26 -30
  22. data/lib/jerakia/log.rb +21 -22
  23. data/lib/jerakia/lookup/plugin/hiera.rb +3 -4
  24. data/lib/jerakia/lookup/plugin.rb +5 -6
  25. data/lib/jerakia/lookup/plugin_config.rb +31 -0
  26. data/lib/jerakia/lookup/pluginfactory.rb +30 -36
  27. data/lib/jerakia/lookup.rb +31 -32
  28. data/lib/jerakia/policy.rb +60 -45
  29. data/lib/jerakia/request.rb +3 -2
  30. data/lib/jerakia/response/filter/encryption.rb +7 -12
  31. data/lib/jerakia/response/filter/strsub.rb +4 -9
  32. data/lib/jerakia/response/filter.rb +5 -5
  33. data/lib/jerakia/response.rb +7 -13
  34. data/lib/jerakia/schema.rb +23 -35
  35. data/lib/jerakia/scope/metadata.rb +0 -1
  36. data/lib/jerakia/scope/puppetdb.rb +38 -0
  37. data/lib/jerakia/scope/server.rb +60 -0
  38. data/lib/jerakia/scope/yaml.rb +3 -4
  39. data/lib/jerakia/scope.rb +0 -2
  40. data/lib/jerakia/server/auth/token.rb +35 -0
  41. data/lib/jerakia/server/auth.rb +72 -0
  42. data/lib/jerakia/server/rest.rb +140 -0
  43. data/lib/jerakia/server.rb +41 -0
  44. data/lib/jerakia/util.rb +6 -7
  45. data/lib/jerakia/version.rb +1 -3
  46. data/lib/jerakia.rb +58 -40
  47. data/lib/puppet/indirector/data_binding/jerakia.rb +9 -11
  48. data/lib/puppet/indirector/data_binding/jerakia_rest.rb +11 -13
  49. metadata +78 -11
data/lib/jerakia/log.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  class Jerakia::Log < Jerakia
2
-
3
2
  require 'logger'
4
- def initialize(level=:info,file='/var/log/jerakia.log')
3
+ def initialize(level = :info, file = '/var/log/jerakia.log')
5
4
  begin
6
5
  @@logger ||= Logger.new(file)
7
6
  rescue Errno::EACCES => e
@@ -19,6 +18,10 @@ class Jerakia::Log < Jerakia
19
18
  end
20
19
  end
21
20
 
21
+ def logger
22
+ @@logger
23
+ end
24
+
22
25
  def verbose(msg)
23
26
  @@logger.info msg if @@level == :verbose
24
27
  end
@@ -39,24 +42,20 @@ class Jerakia::Log < Jerakia
39
42
  @@logger.fatal msg
40
43
  end
41
44
 
42
- # def self.fatal(msg)
43
- # self.new.fatal msg
44
- # end
45
- #
46
- # def self.error(msg)
47
- # self.new.error msg
48
- # end
49
- #
50
- # def self.debug(msg)
51
- # self.new.debug msg
52
- # end
53
- #
54
- ## def self.info(msg)
55
- # puts @@logger
56
- # self.new.info msg
57
- # end
58
-
59
-
60
-
45
+ # def self.fatal(msg)
46
+ # self.new.fatal msg
47
+ # end
48
+ #
49
+ # def self.error(msg)
50
+ # self.new.error msg
51
+ # end
52
+ #
53
+ # def self.debug(msg)
54
+ # self.new.debug msg
55
+ # end
56
+ #
57
+ ## def self.info(msg)
58
+ # puts @@logger
59
+ # self.new.info msg
60
+ # end
61
61
  end
62
-
@@ -8,16 +8,15 @@
8
8
  #
9
9
  class Jerakia::Lookup::Plugin
10
10
  module Hiera
11
-
12
11
  def autorun
13
- if request.namespace.length > 0
12
+ unless request.namespace.empty?
14
13
  request.key.prepend("#{request.namespace.join('::')}::")
15
14
  end
16
- request.namespace=[]
15
+ request.namespace = []
17
16
  end
18
17
 
19
18
  def rewrite_lookup
20
- Jerakia.log.debug("DEPRECATION NOTICE: The use of plugin.hiera.rewrite_lookup is now deprecated and is automatically executed when the plugin is loaded")
19
+ Jerakia.log.debug('DEPRECATION NOTICE: The use of plugin.hiera.rewrite_lookup is now deprecated and is automatically executed when the plugin is loaded')
21
20
  end
22
21
 
23
22
  def calling_module
@@ -1,16 +1,17 @@
1
1
  class Jerakia::Lookup::Plugin
2
-
3
-
4
2
  attr_reader :lookup
5
-
6
- def initialize(lookup)
3
+ attr_reader :config
4
+
5
+ def initialize(lookup, config)
7
6
  @lookup = lookup
7
+ @config = config
8
8
  end
9
9
 
10
10
  def activate(name)
11
11
  instance_eval "extend Jerakia::Lookup::Plugin::#{name.to_s.capitalize}"
12
12
  end
13
13
 
14
+
14
15
  def scope
15
16
  lookup.scope
16
17
  end
@@ -18,6 +19,4 @@ class Jerakia::Lookup::Plugin
18
19
  def request
19
20
  lookup.request
20
21
  end
21
-
22
22
  end
23
-
@@ -0,0 +1,31 @@
1
+ # Jerakia::Lookup::PluginConfig
2
+ #
3
+ # This class is a simple wrapper class to expose configuration options
4
+ # from the global configuration file to lookup plugins. It's exposed
5
+ # to the lookup as the config method. Eg: config[:foo]
6
+ #
7
+ class Jerakia
8
+ class Lookup
9
+ class PluginConfig
10
+
11
+ attr_reader :plugin_name
12
+ attr_reader :config
13
+
14
+ def initialize(plugin_name)
15
+ @plugin_name = plugin_name
16
+ @config = {}
17
+ if Jerakia.config[:plugins].is_a?(Hash)
18
+ @config = Jerakia.config[:plugins][plugin_name.to_s] || {}
19
+ end
20
+ end
21
+
22
+ def [](key)
23
+ config[key.to_s]
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+
30
+
31
+
@@ -1,38 +1,32 @@
1
1
  class Jerakia::Lookup::PluginFactory
2
-
3
- def initialize
4
- Jerakia.log.debug("Loaded plugin handler")
5
- @plugin_config = Jerakia.config[:plugins] || {}
6
- end
7
-
8
-
9
- def create_plugin_method(name, &block)
10
- self.class.send(:define_method, name, &block)
11
- end
12
-
13
- def register(name,plugin)
14
- begin
15
- require "jerakia/lookup/plugin/#{name}"
16
- rescue LoadError => e
17
- raise Jerakia::Error, "Cannot load plugin #{name}, #{e.message}"
18
- end
19
-
20
-
21
- plugin.activate(name)
22
- create_plugin_method(name) do
23
- plugin
24
- end
25
- if plugin.respond_to?('autorun')
26
- Jerakia.log.debug("Found autorun method for plugin #{name}, executing")
27
-
28
- if plugin.method('autorun').arity == 1
29
- plugin.autorun (@plugin_config[name.to_s] || {} )
30
- else
31
- plugin.autorun
32
- end
33
- end
34
- end
35
-
2
+ def initialize
3
+ Jerakia.log.debug('Loaded plugin handler')
4
+ @plugin_config = Jerakia.config[:plugins] || {}
5
+ end
6
+
7
+ def create_plugin_method(name, &block)
8
+ self.class.send(:define_method, name, &block)
9
+ end
10
+
11
+ def register(name, plugin)
12
+ begin
13
+ require "jerakia/lookup/plugin/#{name}"
14
+ rescue LoadError => e
15
+ raise Jerakia::Error, "Cannot load plugin #{name}, #{e.message}"
16
+ end
17
+
18
+ plugin.activate(name)
19
+ create_plugin_method(name) do
20
+ plugin
21
+ end
22
+ if plugin.respond_to?('autorun')
23
+ Jerakia.log.debug("Found autorun method for plugin #{name}, executing")
24
+
25
+ if plugin.method('autorun').arity == 1
26
+ plugin.autorun (@plugin_config[name.to_s] || {})
27
+ else
28
+ plugin.autorun
29
+ end
30
+ end
31
+ end
36
32
  end
37
-
38
-
@@ -3,6 +3,7 @@ class Jerakia::Lookup
3
3
  require 'jerakia/scope'
4
4
  require 'jerakia/lookup/plugin'
5
5
  require 'jerakia/lookup/pluginfactory'
6
+ require 'jerakia/lookup/plugin_config'
6
7
 
7
8
  attr_accessor :request
8
9
  attr_accessor :datasource
@@ -15,20 +16,18 @@ class Jerakia::Lookup
15
16
  attr_reader :pluginfactory
16
17
  attr_reader :datasource
17
18
 
18
- def initialize(name,opts,req,scope)
19
-
20
- @name=name
21
- @request=req
22
- @valid=true
23
- @scope_object=scope
24
- @output_filters=[]
25
- @proceed=true
19
+ def initialize(name, opts, req, scope)
20
+ @name = name
21
+ @request = req
22
+ @valid = true
23
+ @scope_object = scope
24
+ @output_filters = []
25
+ @proceed = true
26
26
  @pluginfactory = Jerakia::Lookup::PluginFactory.new
27
27
 
28
-
29
28
  # Validate options passed to the lookup
30
29
  #
31
- valid_opts = [ :use ]
30
+ valid_opts = [:use]
32
31
 
33
32
  opts.keys.each do |opt_key|
34
33
  unless valid_opts.include?(opt_key)
@@ -36,17 +35,23 @@ class Jerakia::Lookup
36
35
  end
37
36
  end
38
37
 
39
-
40
38
  if opts[:use]
41
- Array(opts[:use]).flatten.each do |plugin|
39
+ Array(opts[:use]).flatten.each do |plugin|
42
40
  plugin_load(plugin)
43
41
  end
44
42
  end
45
43
  end
46
-
44
+
45
+ # Retrieve plugin specific configuration from the global configuration file
46
+ # gets passed to the plugin instance upon initilization.
47
+ #
48
+ def plugin_config(plugin)
49
+ Jerakia::Lookup::PluginConfig.new(plugin)
50
+ end
51
+
47
52
  def plugin_load(plugin)
48
53
  Jerakia.log.debug("Loading plugin #{plugin}")
49
- pluginfactory.register(plugin, Jerakia::Lookup::Plugin.new(self))
54
+ pluginfactory.register(plugin, Jerakia::Lookup::Plugin.new(self, plugin_config(plugin)))
50
55
  end
51
56
 
52
57
  def plugin
@@ -57,7 +62,7 @@ class Jerakia::Lookup
57
62
  @datasource
58
63
  end
59
64
 
60
- def datasource(source, opts={})
65
+ def datasource(source, opts = {})
61
66
  @datasource = Jerakia::Datasource.new(source, self, opts)
62
67
  end
63
68
 
@@ -69,11 +74,10 @@ class Jerakia::Lookup
69
74
  scope_object.value
70
75
  end
71
76
 
72
-
73
- def output_filter(name,opts={})
77
+ def output_filter(name, opts = {})
74
78
  @output_filters << { :name => name, :opts => opts }
75
79
  end
76
-
80
+
77
81
  def proceed?
78
82
  proceed
79
83
  end
@@ -109,38 +113,33 @@ class Jerakia::Lookup
109
113
  valid
110
114
  end
111
115
 
112
-
113
- def get_matches(key,match)
114
- matches = Array(match).select { |m| key[Regexp.new(m)] == key}
116
+ def get_matches(key, match)
117
+ matches = Array(match).select { |m| key[Regexp.new(m)] == key }
115
118
  end
116
-
117
- def confine(key=nil,match)
119
+
120
+ def confine(key = nil, match)
118
121
  if key
119
- invalidate unless get_matches(key,match).size > 0
122
+ invalidate if get_matches(key, match).empty?
120
123
  else
121
124
  invalidate
122
125
  end
123
126
  end
124
127
 
125
- def exclude(key=nil,match)
128
+ def exclude(key = nil, match)
126
129
  if key
127
- invalidate if get_matches(key,match).size > 0
130
+ invalidate unless get_matches(key, match).empty?
128
131
  end
129
132
  end
130
-
131
133
 
132
134
  def run
133
135
  Jerakia.log.verbose("lookup: #{@name} key: #{@request.key} namespace: #{@request.namespace.join('/')}")
134
136
  @datasource.run
135
- response=@datasource.response
137
+ response = @datasource.response
136
138
  @output_filters.each do |filter|
137
139
  response.filter! filter[:name], filter[:opts]
138
140
  end
139
- return response
141
+ response
140
142
  end
141
143
 
142
-
143
144
  private
144
-
145
145
  end
146
-
@@ -2,69 +2,84 @@ require 'jerakia/launcher'
2
2
  require 'jerakia/answer'
3
3
  require 'jerakia/schema'
4
4
 
5
- class Jerakia::Policy
5
+ class Jerakia
6
+ class Policy
7
+ attr_accessor :lookups
8
+ attr_reader :answer
9
+ attr_reader :scope
10
+ attr_reader :lookup_proceed
11
+ attr_reader :schema
12
+ attr_reader :request
6
13
 
7
- attr_accessor :lookups
8
- attr_reader :answer
9
- attr_reader :scope
10
- attr_reader :lookup_proceed
11
- attr_reader :schema
12
- attr_reader :request
13
-
14
- def initialize(name, opts={}, req)
14
+ # _opts currently does not get used, but is included here as a placeholder
15
+ # for allowing policies to be declared with options;
16
+ # policy :foo, :option => :value do
17
+ #
18
+ def initialize(_name, _opts, req)
19
+ if req.use_schema && Jerakia.config[:enable_schema]
20
+ schema_config = Jerakia.config[:schema] || {}
21
+ @schema = Jerakia::Schema.new(req, schema_config)
22
+ end
15
23
 
16
- if req.use_schema and Jerakia.config[:enable_schema]
17
- schema_config = Jerakia.config[:schema] || {}
18
- @schema = Jerakia::Schema.new(req, schema_config)
24
+ @lookups = []
25
+ @request = req
26
+ @answer = Jerakia::Answer.new(req.lookup_type)
27
+ @scope = Jerakia::Scope.new(req)
28
+ @lookup_proceed = true
19
29
  end
20
30
 
31
+ def clone_request
32
+ Marshal.load(Marshal.dump(request))
33
+ end
21
34
 
22
- @lookups=[]
23
- @request=req
24
- @answer=Jerakia::Answer.new(req.lookup_type)
25
- @scope=Jerakia::Scope.new(req)
26
- @lookup_proceed = true
27
- end
28
-
29
- def clone_request
30
- Marshal.load(Marshal.dump(request))
31
- end
35
+ def submit_lookup(lookup)
36
+ raise Jerakia::PolicyError, "Lookup #{lookup.name} has no datasource defined" unless lookup.get_datasource
37
+ @lookups << lookup if lookup.valid? && @lookup_proceed
38
+ @lookup_proceed = false if !lookup.proceed? && lookup.valid?
39
+ end
32
40
 
33
- def submit_lookup(lookup)
34
- raise Jerakia::PolicyError, "Lookup #{lookup.name} has no datasource defined" unless lookup.get_datasource
35
- @lookups << lookup if lookup.valid? and @lookup_proceed
36
- @lookup_proceed = false if !lookup.proceed? and lookup.valid?
37
- end
41
+ def execute
42
+ response_entries = []
38
43
 
39
- def fire!
40
- response_entries = []
44
+ @lookups.each do |l|
45
+ responses = l.run
46
+ lookup_answers = responses.entries.map { |r| r }
47
+ response_entries << lookup_answers if lookup_answers
48
+ end
41
49
 
42
- @lookups.each do |l|
43
- responses = l.run
44
- lookup_answers = responses.entries.map { |r| r }
45
- response_entries << lookup_answers if lookup_answers
50
+ response_entries.flatten.each { |res| process_response(res) }
51
+ consolidate_answer
46
52
  end
47
53
 
54
+ private
48
55
 
49
- response_entries.flatten.each do |res|
56
+ # Process the response depending on the requests lookup_type
57
+ # if it is a :first lookup then we only want to set the result
58
+ # once, if it's cascading, we should ammend the payload array
59
+ #
60
+ def process_response(res)
50
61
  case request.lookup_type
51
62
  when :first
52
- @answer.payload ||= res[:value]
53
- @answer.datatype ||= res[:datatype]
63
+ @answer.payload ||= res[:value]
64
+ @answer.datatype ||= res[:datatype]
54
65
  when :cascade
55
- @answer.payload << res[:value]
66
+ @answer.payload << res[:value]
56
67
  end
57
68
  end
58
69
 
59
- if request.lookup_type == :cascade && @answer.payload.is_a?(Array)
60
- case request.merge
61
- when :array
62
- @answer.flatten_payload!
63
- when :hash,:deep_hash
64
- @answer.merge_payload!(request.merge)
70
+ # Once all the responses are submitted into the answers payload
71
+ # we need to consolidate the data based on the merge behaviour
72
+ # requested.
73
+ #
74
+ def consolidate_answer
75
+ if request.lookup_type == :cascade && @answer.payload.is_a?(Array)
76
+ case request.merge
77
+ when :array
78
+ @answer.flatten_payload!
79
+ when :hash, :deep_hash
80
+ @answer.merge_payload!(request.merge)
81
+ end
65
82
  end
66
83
  end
67
-
68
84
  end
69
85
  end
70
-
@@ -1,6 +1,7 @@
1
+ require 'jerakia'
2
+ require 'jerakia/log'
1
3
  class Jerakia
2
4
  class Request
3
-
4
5
  attr_accessor :key
5
6
  attr_accessor :namespace
6
7
  attr_accessor :merge
@@ -11,7 +12,7 @@ class Jerakia
11
12
  attr_accessor :scope_options
12
13
  attr_accessor :use_schema
13
14
 
14
- def initialize(opts={})
15
+ def initialize(opts = {})
15
16
  options = defaults.merge(opts)
16
17
  @key = options[:key]
17
18
  @namespace = options[:namespace]
@@ -17,30 +17,27 @@ require 'yaml'
17
17
  class Jerakia::Response
18
18
  module Filter
19
19
  module Encryption
20
-
21
- def filter_encryption(opts={})
20
+ def filter_encryption(_opts = {})
22
21
  parse_values do |val|
23
- if val.is_a?(String)
24
- decrypt val
25
- end
22
+ decrypt val if val.is_a?(String)
26
23
  val
27
24
  end
28
25
  end
29
26
 
30
27
  def decrypt(data)
31
28
  if encrypted?(data)
32
- public_key = config["eyaml"]["public_key"]
33
- private_key = config["eyaml"]["private_key"]
29
+ public_key = config['eyaml']['public_key']
30
+ private_key = config['eyaml']['private_key']
34
31
  Hiera::Backend::Eyaml::Options[:pkcs7_private_key] = private_key
35
32
  Hiera::Backend::Eyaml::Options[:pkcs7_public_key] = public_key
36
33
  parser = Hiera::Backend::Eyaml::Parser::ParserFactory.hiera_backend_parser
37
-
34
+
38
35
  tokens = parser.parse(data)
39
- decrypted = tokens.map{ |token| token.to_plain_text }
36
+ decrypted = tokens.map(&:to_plain_text)
40
37
  plaintext = decrypted.join
41
38
  Jerakia.log.debug(plaintext)
42
39
  plaintext.chomp!
43
- data.clear.insert(0,plaintext)
40
+ data.clear.insert(0, plaintext)
44
41
  else
45
42
  data
46
43
  end
@@ -52,5 +49,3 @@ class Jerakia::Response
52
49
  end
53
50
  end
54
51
  end
55
-
56
-
@@ -1,6 +1,6 @@
1
1
  # strsub is in output filter that matches tags in data and replaces them
2
2
  # for values in the scope. It mimics the hiera features of being able to
3
- # embed %{::var} in YAML documents. This output filter may not provide
3
+ # embed %{::var} in YAML documents. This output filter may not provide
4
4
  # 100% compatibility to hiera but it should cover most scenarios.
5
5
  #
6
6
  # Jerakia does not support method or literal interpolations, just straightforward %{var} and %{::var}
@@ -10,12 +10,9 @@
10
10
  class Jerakia::Response
11
11
  module Filter
12
12
  module Strsub
13
-
14
- def filter_strsub(opts={})
13
+ def filter_strsub(_opts = {})
15
14
  parse_values do |val|
16
- if val.is_a?(String)
17
- do_substr(val)
18
- end
15
+ do_substr(val) if val.is_a?(String)
19
16
  val
20
17
  end
21
18
  end
@@ -24,12 +21,10 @@ class Jerakia::Response
24
21
  data.gsub!(/%\{([^\}]*)\}/) do |tag|
25
22
  Jerakia.log.debug("matched substr #{tag}")
26
23
  scopekey = tag.match(/\{([^\}]+)\}/)[1]
27
- scopekey.gsub!(/^::/,'')
24
+ scopekey.gsub!(/^::/, '')
28
25
  lookup.scope[scopekey.to_sym]
29
26
  end
30
27
  end
31
28
  end
32
29
  end
33
30
  end
34
-
35
-
@@ -1,9 +1,9 @@
1
1
  class Jerakia::Response
2
2
  module Filter
3
- def filter!(name,opts)
4
- Jerakia::Util.autoload('response/filter', name)
5
- instance_eval "extend Jerakia::Response::Filter::#{name.to_s.capitalize}"
6
- instance_eval "self.filter_#{name.to_s} (#{opts})"
7
- end
3
+ def filter!(name, opts)
4
+ Jerakia::Util.autoload('response/filter', name)
5
+ instance_eval "extend Jerakia::Response::Filter::#{name.to_s.capitalize}"
6
+ instance_eval "self.filter_#{name} (#{opts})"
7
+ end
8
8
  end
9
9
  end
@@ -1,17 +1,16 @@
1
1
  class Jerakia::Response < Jerakia
2
-
3
2
  attr_accessor :entries
4
3
  attr_reader :lookup
5
4
 
6
5
  def initialize(lookup)
7
- @entries=[]
8
- @lookup=lookup
6
+ @entries = []
7
+ @lookup = lookup
9
8
  require 'jerakia/response/filter'
10
9
  extend Jerakia::Response::Filter
11
10
  end
12
11
 
13
12
  def want?
14
- if lookup.request.lookup_type == :first && entries.length > 0
13
+ if lookup.request.lookup_type == :first && !entries.empty?
15
14
  return false
16
15
  else
17
16
  return true
@@ -20,14 +19,14 @@ class Jerakia::Response < Jerakia
20
19
 
21
20
  def submit(val)
22
21
  Jerakia.log.debug "Backend submitted #{val}"
23
- unless want?
24
- no_more_answers
25
- else
22
+ if want?
26
23
  @entries << {
27
24
  :value => val,
28
25
  :datatype => val.class.to_s.downcase
29
26
  }
30
27
  Jerakia.log.debug "Added answer #{val}"
28
+ else
29
+ no_more_answers
31
30
  end
32
31
  end
33
32
 
@@ -44,14 +43,9 @@ class Jerakia::Response < Jerakia
44
43
  end
45
44
  entry
46
45
  end
47
-
48
46
  end
49
-
50
-
51
47
 
52
48
  def no_more_answers
53
- Jerakia.log.debug "warning: backend tried to submit too many answers"
49
+ Jerakia.log.debug 'warning: backend tried to submit too many answers'
54
50
  end
55
-
56
51
  end
57
-