choria-mcorpc-support 2.23.0 → 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.
- 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
|