choria-mcorpc-support 2.23.0 → 2.23.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/mcollective.rb +1 -2
- data/lib/mcollective/agent/bolt_tasks.ddl +18 -0
- data/lib/mcollective/agent/bolt_tasks.json +18 -0
- data/lib/mcollective/agent/bolt_tasks.rb +4 -2
- data/lib/mcollective/agent/rpcutil.ddl +2 -2
- data/lib/mcollective/agent/rpcutil.json +2 -2
- data/lib/mcollective/application/choria.rb +3 -63
- data/lib/mcollective/application/federation.rb +1 -3
- data/lib/mcollective/application/ping.rb +31 -3
- data/lib/mcollective/application/tasks.rb +9 -0
- data/lib/mcollective/discovery.rb +12 -13
- data/lib/mcollective/message.rb +0 -24
- data/lib/mcollective/optionparser.rb +1 -1
- data/lib/mcollective/rpc/client.rb +2 -2
- data/lib/mcollective/security/base.rb +1 -37
- data/lib/mcollective/util/choria.rb +0 -157
- data/lib/mcollective/util/tasks_support.rb +21 -3
- metadata +2 -6
- data/lib/mcollective/application/describe_filter.rb +0 -87
- data/lib/mcollective/matcher.rb +0 -220
- data/lib/mcollective/matcher/parser.rb +0 -118
- data/lib/mcollective/matcher/scanner.rb +0 -236
@@ -86,14 +86,6 @@ module MCollective
|
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
|
-
# Which port to provide stats over HTTP on
|
90
|
-
#
|
91
|
-
# @return [Integer,nil]
|
92
|
-
# @raise [StandardError] when not numeric
|
93
|
-
def stats_port
|
94
|
-
Integer(get_option("choria.stats_port", "")) if has_option?("choria.stats_port")
|
95
|
-
end
|
96
|
-
|
97
89
|
# Determines if there are any federations configured
|
98
90
|
#
|
99
91
|
# @return [Boolean]
|
@@ -888,15 +880,6 @@ module MCollective
|
|
888
880
|
File.exist?(csr_path)
|
889
881
|
end
|
890
882
|
|
891
|
-
# The formatted string representation of the CSR fingerprint
|
892
|
-
#
|
893
|
-
# @return [String]
|
894
|
-
def csr_fingerprint
|
895
|
-
require "puppet"
|
896
|
-
csr = OpenSSL::X509::Request.new(File.read(csr_path))
|
897
|
-
Puppet::SSL::Digest.new(nil, csr.to_der)
|
898
|
-
end
|
899
|
-
|
900
883
|
# Searches the PATH for an executable command
|
901
884
|
#
|
902
885
|
# @param command [String] a command to search for
|
@@ -927,146 +910,6 @@ module MCollective
|
|
927
910
|
which("facter")
|
928
911
|
end
|
929
912
|
|
930
|
-
# Creates any missing SSL directories
|
931
|
-
#
|
932
|
-
# This prepares a Puppet like SSL tree in case Puppet
|
933
|
-
# has not been initialized yet
|
934
|
-
#
|
935
|
-
# @return [void]
|
936
|
-
def make_ssl_dirs
|
937
|
-
return if file_security?
|
938
|
-
|
939
|
-
FileUtils.mkdir_p(ssl_dir, :mode => 0o0771)
|
940
|
-
|
941
|
-
["certificate_requests", "certs", "public_keys"].each do |dir|
|
942
|
-
FileUtils.mkdir_p(File.join(ssl_dir, dir), :mode => 0o0755)
|
943
|
-
end
|
944
|
-
|
945
|
-
["private_keys", "private"].each do |dir|
|
946
|
-
FileUtils.mkdir_p(File.join(ssl_dir, dir), :mode => 0o0750)
|
947
|
-
end
|
948
|
-
end
|
949
|
-
|
950
|
-
# Creates a RSA key of a certain strenth
|
951
|
-
#
|
952
|
-
# @return [OpenSSL::PKey::RSA]
|
953
|
-
def create_rsa_key(bits)
|
954
|
-
OpenSSL::PKey::RSA.new(bits)
|
955
|
-
end
|
956
|
-
|
957
|
-
# Writes a new 4096 bit key in the puppet default locatioj
|
958
|
-
#
|
959
|
-
# @return [OpenSSL::PKey::RSA]
|
960
|
-
# @raise [StandardError] when the key already exist
|
961
|
-
def write_key
|
962
|
-
raise("Refusing to overwrite existing key in %s" % client_private_key) if has_client_private_key?
|
963
|
-
|
964
|
-
key = create_rsa_key(4096)
|
965
|
-
File.open(client_private_key, "w", 0o0640) {|f| f.write(key.to_pem)}
|
966
|
-
|
967
|
-
key
|
968
|
-
end
|
969
|
-
|
970
|
-
# Creates a basic CSR
|
971
|
-
#
|
972
|
-
# @return [OpenSSL::X509::Request] signed CSR
|
973
|
-
def create_csr(comonname, orgunit, key)
|
974
|
-
csr = OpenSSL::X509::Request.new
|
975
|
-
csr.version = 0
|
976
|
-
csr.public_key = key.public_key
|
977
|
-
csr.subject = OpenSSL::X509::Name.new(
|
978
|
-
[
|
979
|
-
["CN", comonname, OpenSSL::ASN1::UTF8STRING],
|
980
|
-
["OU", orgunit, OpenSSL::ASN1::UTF8STRING]
|
981
|
-
]
|
982
|
-
)
|
983
|
-
csr.sign(key, OpenSSL::Digest.new("SHA1"))
|
984
|
-
|
985
|
-
csr
|
986
|
-
end
|
987
|
-
|
988
|
-
# Creates a new CSR signed by the given key
|
989
|
-
#
|
990
|
-
# @param key [OpenSSL::PKey::RSA]
|
991
|
-
# @return [String] PEM encoded CSR
|
992
|
-
def write_csr(key)
|
993
|
-
raise("Refusing to overwrite existing CSR in %s" % csr_path) if has_csr?
|
994
|
-
|
995
|
-
csr = create_csr(certname, "mcollective", key)
|
996
|
-
|
997
|
-
File.open(csr_path, "w", 0o0644) {|f| f.write(csr.to_pem)}
|
998
|
-
|
999
|
-
csr.to_pem
|
1000
|
-
end
|
1001
|
-
|
1002
|
-
# Fetch and save the CA from Puppet
|
1003
|
-
#
|
1004
|
-
# @return [Boolean]
|
1005
|
-
def fetch_ca
|
1006
|
-
return true if has_ca?
|
1007
|
-
|
1008
|
-
server = puppetca_server
|
1009
|
-
|
1010
|
-
req = http_get("/puppet-ca/v1/certificate/ca?environment=production", "Accept" => "text/plain")
|
1011
|
-
resp, _ = https(server).request(req)
|
1012
|
-
|
1013
|
-
if resp.code == "200"
|
1014
|
-
File.open(ca_path, "w", 0o0644) {|f| f.write(resp.body)}
|
1015
|
-
else
|
1016
|
-
raise(UserError, "Failed to fetch CA from %s:%s: %s: %s" % [server[:target], server[:port], resp.code, resp.message])
|
1017
|
-
end
|
1018
|
-
|
1019
|
-
has_ca?
|
1020
|
-
end
|
1021
|
-
|
1022
|
-
# Requests a certificate from the Puppet CA
|
1023
|
-
#
|
1024
|
-
# This will attempt to create a new key, write a CSR and
|
1025
|
-
# then sends it to the CA for signing
|
1026
|
-
#
|
1027
|
-
# @return [Boolean]
|
1028
|
-
# @raise [UserError] when requesting the cert fails
|
1029
|
-
def request_cert
|
1030
|
-
key = write_key
|
1031
|
-
csr = write_csr(key)
|
1032
|
-
|
1033
|
-
server = puppetca_server
|
1034
|
-
|
1035
|
-
req = Net::HTTP::Put.new("/puppet-ca/v1/certificate_request/%s?environment=production" % certname, "Content-Type" => "text/plain")
|
1036
|
-
req.body = csr
|
1037
|
-
resp, _ = https(server).request(req)
|
1038
|
-
|
1039
|
-
if resp.code == "200"
|
1040
|
-
true
|
1041
|
-
else
|
1042
|
-
raise(UserError, "Failed to request certificate from %s:%s: %s: %s: %s" % [server[:target], server[:port], resp.code, resp.message, resp.body])
|
1043
|
-
end
|
1044
|
-
end
|
1045
|
-
|
1046
|
-
# Attempts to fetch a cert from the CA
|
1047
|
-
#
|
1048
|
-
# @return [Boolean]
|
1049
|
-
def attempt_fetch_cert
|
1050
|
-
return true if has_client_public_cert?
|
1051
|
-
|
1052
|
-
req = http_get("/puppet-ca/v1/certificate/%s?environment=production" % certname, "Accept" => "text/plain")
|
1053
|
-
resp, _ = https(puppetca_server).request(req)
|
1054
|
-
|
1055
|
-
if resp.code == "200"
|
1056
|
-
File.open(client_public_cert, "w", 0o0644) {|f| f.write(resp.body)}
|
1057
|
-
true
|
1058
|
-
else
|
1059
|
-
false
|
1060
|
-
end
|
1061
|
-
end
|
1062
|
-
|
1063
|
-
# Determines if a CSR has been sent but not yet retrieved
|
1064
|
-
#
|
1065
|
-
# @return [Boolean]
|
1066
|
-
def waiting_for_cert?
|
1067
|
-
!has_client_public_cert? && has_client_private_key?
|
1068
|
-
end
|
1069
|
-
|
1070
913
|
# Gets a config option
|
1071
914
|
#
|
1072
915
|
# @param opt [String] config option to look up
|
@@ -250,8 +250,9 @@ module MCollective
|
|
250
250
|
# @param environment [Hash] environment to run with
|
251
251
|
# @param stdin [String] stdin to send to the command
|
252
252
|
# @param spooldir [String] path to the spool for this specific request
|
253
|
+
# @param run_as [String] name of the user who will run the command
|
253
254
|
# @return [Integer] the pid that was spawned
|
254
|
-
def spawn_command(command, environment, stdin, spooldir)
|
255
|
+
def spawn_command(command, environment, stdin, spooldir, run_as)
|
255
256
|
wrapper_input = File.join(spooldir, "wrapper_stdin")
|
256
257
|
wrapper_stdout = File.join(spooldir, "wrapper_stdout")
|
257
258
|
wrapper_stderr = File.join(spooldir, "wrapper_stderr")
|
@@ -269,7 +270,24 @@ module MCollective
|
|
269
270
|
options[:in] = wrapper_input
|
270
271
|
end
|
271
272
|
|
272
|
-
|
273
|
+
if run_as
|
274
|
+
raise("System does not allow forking. run_as not usable.") unless Process.respond_to?(:fork)
|
275
|
+
|
276
|
+
require "etc"
|
277
|
+
|
278
|
+
u = Etc.getpwnam(run_as)
|
279
|
+
|
280
|
+
FileUtils.chown_R(u.uid, u.gid, spooldir)
|
281
|
+
|
282
|
+
pid = Process.fork
|
283
|
+
if pid.nil?
|
284
|
+
Process.gid = Process.egid = u.gid
|
285
|
+
Process.uid = Process.euid = u.uid
|
286
|
+
Process.exec(environment, command, options)
|
287
|
+
end
|
288
|
+
else
|
289
|
+
pid = Process.spawn(environment, command, options)
|
290
|
+
end
|
273
291
|
|
274
292
|
sleep 0.1 until File.exist?(wrapper_stdout)
|
275
293
|
|
@@ -353,7 +371,7 @@ module MCollective
|
|
353
371
|
meta.print(data.to_json)
|
354
372
|
end
|
355
373
|
|
356
|
-
pid = spawn_command(wrapper_path, task_environment(task, requestid, callerid), wrapper_input.to_json, spool)
|
374
|
+
pid = spawn_command(wrapper_path, task_environment(task, requestid, callerid), wrapper_input.to_json, spool, task["run_as"])
|
357
375
|
|
358
376
|
Log.info("Spawned task %s in spool %s with pid %s" % [task["task"], spool, pid])
|
359
377
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: choria-mcorpc-support
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.23.
|
4
|
+
version: 2.23.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- R.I.Pienaar
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: systemu
|
@@ -79,7 +79,6 @@ files:
|
|
79
79
|
- lib/mcollective/application.rb
|
80
80
|
- lib/mcollective/application/choria.rb
|
81
81
|
- lib/mcollective/application/completion.rb
|
82
|
-
- lib/mcollective/application/describe_filter.rb
|
83
82
|
- lib/mcollective/application/facts.rb
|
84
83
|
- lib/mcollective/application/federation.rb
|
85
84
|
- lib/mcollective/application/find.rb
|
@@ -145,9 +144,6 @@ files:
|
|
145
144
|
- lib/mcollective/logger/console_logger.rb
|
146
145
|
- lib/mcollective/logger/file_logger.rb
|
147
146
|
- lib/mcollective/logger/syslog_logger.rb
|
148
|
-
- lib/mcollective/matcher.rb
|
149
|
-
- lib/mcollective/matcher/parser.rb
|
150
|
-
- lib/mcollective/matcher/scanner.rb
|
151
147
|
- lib/mcollective/message.rb
|
152
148
|
- lib/mcollective/monkey_patches.rb
|
153
149
|
- lib/mcollective/optionparser.rb
|
@@ -1,87 +0,0 @@
|
|
1
|
-
module MCollective
|
2
|
-
class Application::Describe_filter < Application # rubocop:disable Style/ClassAndModuleChildren
|
3
|
-
exclude_argument_sections "common", "rpc"
|
4
|
-
|
5
|
-
description "Display human readable interpretation of filters"
|
6
|
-
|
7
|
-
usage "mco describe_filter -S <filter> -F <filter> -C <filter>"
|
8
|
-
|
9
|
-
def describe_s_filter(stack)
|
10
|
-
indent = " "
|
11
|
-
old_indent = " "
|
12
|
-
puts "-S Query expands to the following instructions:"
|
13
|
-
puts
|
14
|
-
stack.each do |token|
|
15
|
-
case token.keys[0]
|
16
|
-
when "statement"
|
17
|
-
if token.values[0] =~ /(<=|>=|=|=~|=)/
|
18
|
-
op = $1
|
19
|
-
k, v = token.values[0].split(op)
|
20
|
-
puts indent + get_fact_string(k, v, op)
|
21
|
-
else
|
22
|
-
puts indent + get_class_string(token.values[0])
|
23
|
-
end
|
24
|
-
when "fstatement"
|
25
|
-
v = token.values[0]
|
26
|
-
result_string = indent + "Execute the Data Query '#{v['name']}'"
|
27
|
-
result_string += " with parameters (#{v['params']})" if v["params"]
|
28
|
-
result_string += ". "
|
29
|
-
result_string += "Check if the query's '#{v['value']}' value #{v['operator']} '#{v['r_compare']}' "
|
30
|
-
puts result_string
|
31
|
-
when "("
|
32
|
-
puts "#{indent}("
|
33
|
-
old_indent = indent
|
34
|
-
indent *= 2
|
35
|
-
when ")"
|
36
|
-
indent = old_indent
|
37
|
-
puts "#{indent})"
|
38
|
-
else
|
39
|
-
puts indent + token.keys[0].upcase
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def describe_f_filter(facts)
|
45
|
-
puts "-F filter expands to the following fact comparisons:"
|
46
|
-
puts
|
47
|
-
facts.each do |f|
|
48
|
-
puts " #{get_fact_string(f[:fact], f[:value], f[:operator])}"
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def describe_c_filter(classes)
|
53
|
-
puts "-C filter expands to the following class checks:"
|
54
|
-
puts
|
55
|
-
classes.each do |c|
|
56
|
-
puts " #{get_class_string(c)}"
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def main
|
61
|
-
unless @options[:filter]["fact"].empty?
|
62
|
-
describe_f_filter(@options[:filter]["fact"])
|
63
|
-
puts
|
64
|
-
end
|
65
|
-
|
66
|
-
unless @options[:filter]["cf_class"].empty?
|
67
|
-
describe_c_filter(@options[:filter]["cf_class"])
|
68
|
-
puts
|
69
|
-
end
|
70
|
-
|
71
|
-
unless @options[:filter]["compound"].empty?
|
72
|
-
describe_s_filter(@options[:filter]["compound"][0])
|
73
|
-
puts
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
private
|
78
|
-
|
79
|
-
def get_fact_string(fact, value, oper)
|
80
|
-
"Check if fact '#{fact}' #{oper} '#{value}'"
|
81
|
-
end
|
82
|
-
|
83
|
-
def get_class_string(classname)
|
84
|
-
"Check if class '#{classname}' is present on the host"
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
data/lib/mcollective/matcher.rb
DELETED
@@ -1,220 +0,0 @@
|
|
1
|
-
module MCollective
|
2
|
-
# A parser and scanner that creates a stack machine for a simple
|
3
|
-
# fact and class matching language used on the CLI to facilitate
|
4
|
-
# a rich discovery language
|
5
|
-
#
|
6
|
-
# Language EBNF
|
7
|
-
#
|
8
|
-
# compound = ["("] expression [")"] {["("] expression [")"]}
|
9
|
-
# expression = [!|not]statement ["and"|"or"] [!|not] statement
|
10
|
-
# char = A-Z | a-z | < | > | => | =< | _ | - |* | / { A-Z | a-z | < | > | => | =< | _ | - | * | / | }
|
11
|
-
# int = 0|1|2|3|4|5|6|7|8|9{|0|1|2|3|4|5|6|7|8|9|0}
|
12
|
-
module Matcher
|
13
|
-
require "mcollective/matcher/parser"
|
14
|
-
require "mcollective/matcher/scanner"
|
15
|
-
|
16
|
-
# Helper creates a hash from a function call string
|
17
|
-
def self.create_function_hash(function_call)
|
18
|
-
func_hash = {}
|
19
|
-
f = ""
|
20
|
-
func_parts = function_call.split(/(!=|>=|<=|<|>|=)/)
|
21
|
-
func_hash["r_compare"] = func_parts.pop
|
22
|
-
func_hash["operator"] = func_parts.pop
|
23
|
-
func = func_parts.join
|
24
|
-
|
25
|
-
# Deal with dots in function parameters and functions without dot values
|
26
|
-
if func =~ /^.+\(.*\)$/
|
27
|
-
f = func
|
28
|
-
else
|
29
|
-
func_parts = func.split(".")
|
30
|
-
func_hash["value"] = func_parts.pop
|
31
|
-
f = func_parts.join(".")
|
32
|
-
end
|
33
|
-
|
34
|
-
# Deal with regular expression matches
|
35
|
-
if func_hash["r_compare"] =~ /^\/.*\/$/
|
36
|
-
func_hash["operator"] = "=~" if func_hash["operator"] == "="
|
37
|
-
func_hash["operator"] = "!=~" if func_hash["operator"] == "!="
|
38
|
-
func_hash["r_compare"] = Regexp.new(func_hash["r_compare"].gsub(/^\/|\/$/, ""))
|
39
|
-
# Convert = operators to == so they can be propperly evaluated
|
40
|
-
elsif func_hash["operator"] == "="
|
41
|
-
func_hash["operator"] = "=="
|
42
|
-
end
|
43
|
-
|
44
|
-
# Grab function name and parameters from left compare string
|
45
|
-
func_hash["name"], func_hash["params"] = f.split("(")
|
46
|
-
if func_hash["params"] == ")"
|
47
|
-
func_hash["params"] = nil
|
48
|
-
else
|
49
|
-
|
50
|
-
# Walk the function parameters from the front and from the
|
51
|
-
# back removing the first and last instances of single of
|
52
|
-
# double qoutes. We do this to handle the case where params
|
53
|
-
# contain escaped qoutes.
|
54
|
-
func_hash["params"] = func_hash["params"].gsub(")", "")
|
55
|
-
func_quotes = func_hash["params"].split(/('|")/)
|
56
|
-
|
57
|
-
func_quotes.each_with_index do |item, i|
|
58
|
-
if item =~ /'|"/
|
59
|
-
func_quotes.delete_at(i)
|
60
|
-
break
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
func_quotes.reverse.each_with_index do |item, i|
|
65
|
-
if item =~ /'|"/
|
66
|
-
func_quotes.delete_at(func_quotes.size - i - 1)
|
67
|
-
break
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
func_hash["params"] = func_quotes.join
|
72
|
-
end
|
73
|
-
|
74
|
-
func_hash
|
75
|
-
end
|
76
|
-
|
77
|
-
# Returns the result of an executed function
|
78
|
-
def self.execute_function(function_hash)
|
79
|
-
# In the case where a data plugin isn't present there are two ways we can handle
|
80
|
-
# the raised exception. The function result can either be false or the entire
|
81
|
-
# expression can fail.
|
82
|
-
#
|
83
|
-
# In the case where we return the result as false it opens us op to unexpected
|
84
|
-
# negation behavior.
|
85
|
-
#
|
86
|
-
# !foo('bar').name = bar
|
87
|
-
#
|
88
|
-
# In this case the user would expect discovery to match on all machines where
|
89
|
-
# the name value of the foo function does not equal bar. If a non existent function
|
90
|
-
# returns false then it is posible to match machines where the name value of the
|
91
|
-
# foo function is bar.
|
92
|
-
#
|
93
|
-
# Instead we raise a DDLValidationError to prevent this unexpected behavior from
|
94
|
-
# happening.
|
95
|
-
|
96
|
-
result = Data.send(function_hash["name"], function_hash["params"])
|
97
|
-
|
98
|
-
if function_hash["value"]
|
99
|
-
begin
|
100
|
-
eval_result = result.send(function_hash["value"])
|
101
|
-
rescue
|
102
|
-
# If data field has not been set we set the comparison result to nil
|
103
|
-
eval_result = nil
|
104
|
-
end
|
105
|
-
eval_result
|
106
|
-
else
|
107
|
-
result
|
108
|
-
end
|
109
|
-
rescue NoMethodError
|
110
|
-
Log.debug("cannot execute discovery function '#{function_hash['name']}'. data plugin not found")
|
111
|
-
raise DDLValidationError
|
112
|
-
end
|
113
|
-
|
114
|
-
# Evaluates a compound statement
|
115
|
-
def self.eval_compound_statement(expression)
|
116
|
-
case expression.values.first
|
117
|
-
when /^\//
|
118
|
-
Util.has_cf_class?(expression.values.first)
|
119
|
-
when />=|<=|=|<|>/
|
120
|
-
optype = expression.values.first.match(/>=|<=|=|<|>/)
|
121
|
-
name, value = expression.values.first.split(optype[0])
|
122
|
-
if value.split("")[0] == "/"
|
123
|
-
optype = "=~"
|
124
|
-
else
|
125
|
-
optype[0] == "=" ? optype = "==" : optype = optype[0]
|
126
|
-
end
|
127
|
-
|
128
|
-
Util.has_fact?(name, value, optype).to_s
|
129
|
-
else
|
130
|
-
Util.has_cf_class?(expression.values.first)
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
# Returns the result of an evaluated compound statement that
|
135
|
-
# includes a function
|
136
|
-
def self.eval_compound_fstatement(function_hash) # rubocop:disable Metrics/MethodLength
|
137
|
-
l_compare = execute_function(function_hash)
|
138
|
-
r_compare = function_hash["r_compare"]
|
139
|
-
operator = function_hash["operator"]
|
140
|
-
|
141
|
-
# Break out early and return false if the function returns nil
|
142
|
-
return false if l_compare.nil?
|
143
|
-
|
144
|
-
# Prevent unwanted discovery by limiting comparison operators
|
145
|
-
# on Strings and Booleans
|
146
|
-
if (l_compare.is_a?(String) || l_compare.is_a?(TrueClass) || l_compare.is_a?(FalseClass)) && function_hash["operator"].match(/<|>/)
|
147
|
-
Log.debug("Cannot do > and < comparison on Booleans and Strings '#{l_compare} #{function_hash['operator']} #{function_hash['r_compare']}'")
|
148
|
-
return false
|
149
|
-
end
|
150
|
-
|
151
|
-
# Prevent backticks in function parameters
|
152
|
-
if function_hash["params"] =~ /`/
|
153
|
-
Log.debug("Cannot use backticks in function parameters")
|
154
|
-
return false
|
155
|
-
end
|
156
|
-
|
157
|
-
# Do a regex comparison if right compare string is a regex
|
158
|
-
if operator =~ /(=~|!=~)/
|
159
|
-
# Fail if left compare value isn't a string
|
160
|
-
if l_compare.is_a?(String)
|
161
|
-
result = l_compare.match(r_compare)
|
162
|
-
# Flip return value for != operator
|
163
|
-
if function_hash["operator"] == "!=~"
|
164
|
-
!result
|
165
|
-
else
|
166
|
-
!!result
|
167
|
-
end
|
168
|
-
else
|
169
|
-
Log.debug("Cannot do a regex check on a non string value.")
|
170
|
-
false
|
171
|
-
end
|
172
|
-
# Otherwise do a normal comparison while taking the type into account
|
173
|
-
else
|
174
|
-
if l_compare.is_a? String
|
175
|
-
r_compare = r_compare.to_s
|
176
|
-
elsif r_compare.is_a? String
|
177
|
-
case l_compare
|
178
|
-
when Numeric
|
179
|
-
r_compare = r_compare.strip
|
180
|
-
begin
|
181
|
-
r_compare = Integer(r_compare)
|
182
|
-
rescue ArgumentError # rubocop:disable Metrics/BlockNesting
|
183
|
-
begin
|
184
|
-
r_compare = Float(r_compare)
|
185
|
-
rescue ArgumentError # rubocop:disable Metrics/BlockNesting
|
186
|
-
raise(ArgumentError, "invalid numeric value: #{r_compare}")
|
187
|
-
end
|
188
|
-
end
|
189
|
-
when TrueClass, FalseClass
|
190
|
-
r_compare = r_compare.strip
|
191
|
-
if r_compare == true.to_s # rubocop:disable Metrics/BlockNesting
|
192
|
-
r_compare = true
|
193
|
-
elsif r_compare == false.to_s # rubocop:disable Metrics/BlockNesting
|
194
|
-
r_compare = false
|
195
|
-
else
|
196
|
-
raise(ArgumentError, "invalid boolean value: #{r_compare}")
|
197
|
-
end
|
198
|
-
end
|
199
|
-
end
|
200
|
-
operator = operator.strip
|
201
|
-
if operator =~ /(?:(!=|<=|>=|<|>)|==?)/
|
202
|
-
operator = $1 ? $1.to_sym : :==
|
203
|
-
else
|
204
|
-
raise(ArgumentError, "invalid operator: #{operator}")
|
205
|
-
end
|
206
|
-
l_compare.send(operator, r_compare)
|
207
|
-
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
# Creates a callstack to be evaluated from a compound evaluation string
|
212
|
-
def self.create_compound_callstack(call_string)
|
213
|
-
callstack = Matcher::Parser.new(call_string).execution_stack
|
214
|
-
callstack.each_with_index do |statement, i|
|
215
|
-
callstack[i]["fstatement"] = create_function_hash(statement.values.first) if statement.keys.first == "fstatement"
|
216
|
-
end
|
217
|
-
callstack
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|