choria-mcorpc-support 2.20.3 → 2.20.4
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 +18 -19
- data/lib/mcollective/agent.rb +0 -1
- data/lib/mcollective/agents.rb +17 -18
- data/lib/mcollective/aggregate.rb +11 -11
- data/lib/mcollective/application.rb +30 -41
- data/lib/mcollective/applications.rb +13 -14
- data/lib/mcollective/cache.rb +1 -1
- data/lib/mcollective/client.rb +25 -25
- data/lib/mcollective/config.rb +114 -120
- data/lib/mcollective/data.rb +9 -9
- data/lib/mcollective/ddl.rb +1 -1
- data/lib/mcollective/discovery.rb +13 -13
- data/lib/mcollective/exceptions.rb +17 -17
- data/lib/mcollective/facts.rb +2 -2
- data/lib/mcollective/log.rb +7 -9
- data/lib/mcollective/matcher.rb +28 -32
- data/lib/mcollective/message.rb +31 -29
- data/lib/mcollective/monkey_patches.rb +92 -83
- data/lib/mcollective/optionparser.rb +23 -23
- data/lib/mcollective/pluginmanager.rb +8 -11
- data/lib/mcollective/pluginpackager.rb +13 -17
- data/lib/mcollective/rpc.rb +16 -18
- data/lib/mcollective/runnerstats.rb +10 -6
- data/lib/mcollective/shell.rb +30 -33
- data/lib/mcollective/ssl.rb +9 -12
- data/lib/mcollective/util.rb +160 -135
- data/lib/mcollective/validator.rb +18 -20
- metadata +4 -4
@@ -10,7 +10,7 @@ module MCollective
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.[](klass)
|
13
|
-
const_get(
|
13
|
+
const_get(klass.to_s)
|
14
14
|
end
|
15
15
|
|
16
16
|
# Fetch and return metadata from plugin DDL
|
@@ -19,12 +19,12 @@ module MCollective
|
|
19
19
|
|
20
20
|
begin
|
21
21
|
ddl_file = File.read(Dir.glob(File.join(path, type, "*.ddl")).first)
|
22
|
-
rescue Exception
|
22
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
23
23
|
raise "failed to load ddl file in plugin directory : #{File.join(path, type)}"
|
24
24
|
end
|
25
25
|
ddl.instance_eval ddl_file
|
26
26
|
|
27
|
-
|
27
|
+
[ddl.meta, ddl.requirements[:mcollective]]
|
28
28
|
end
|
29
29
|
|
30
30
|
# Checks if a directory is present and not empty
|
@@ -34,19 +34,19 @@ module MCollective
|
|
34
34
|
|
35
35
|
# Quietly calls a block if verbose parameter is false
|
36
36
|
def self.execute_verbosely(verbose, &block)
|
37
|
-
|
37
|
+
if verbose
|
38
|
+
block.call
|
39
|
+
else
|
38
40
|
old_stdout = $stdout.clone
|
39
41
|
$stdout.reopen(File.new("/dev/null", "w"))
|
40
42
|
begin
|
41
43
|
block.call
|
42
|
-
rescue Exception
|
44
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
43
45
|
$stdout.reopen old_stdout
|
44
|
-
raise
|
46
|
+
raise
|
45
47
|
ensure
|
46
48
|
$stdout.reopen old_stdout
|
47
49
|
end
|
48
|
-
else
|
49
|
-
block.call
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
@@ -54,15 +54,13 @@ module MCollective
|
|
54
54
|
def self.command_available?(build_tool)
|
55
55
|
ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
|
56
56
|
builder = File.join(path, build_tool)
|
57
|
-
if File.
|
58
|
-
return true
|
59
|
-
end
|
57
|
+
return true if File.exist?(builder)
|
60
58
|
end
|
61
59
|
false
|
62
60
|
end
|
63
61
|
|
64
62
|
def self.safe_system(*args)
|
65
|
-
raise(
|
63
|
+
raise("Failed: #{args.join(' ')}") unless system(*args)
|
66
64
|
end
|
67
65
|
|
68
66
|
# Filter out platform specific dependencies
|
@@ -77,22 +75,20 @@ module MCollective
|
|
77
75
|
if prefix == $1
|
78
76
|
dependency[:name] = $2
|
79
77
|
dependency
|
80
|
-
else
|
81
|
-
nil
|
82
78
|
end
|
83
79
|
else
|
84
80
|
dependency
|
85
81
|
end
|
86
|
-
end.reject
|
82
|
+
end.reject(&:nil?)
|
87
83
|
end
|
88
84
|
|
89
85
|
# Return the path to a plugin's core directories
|
90
86
|
def self.get_plugin_path(target)
|
91
|
-
if
|
87
|
+
if File.exist?(File.join(target, "lib", "mcollective"))
|
92
88
|
return File.join(target, "lib", "mcollective")
|
93
89
|
end
|
94
90
|
|
95
|
-
|
91
|
+
target
|
96
92
|
end
|
97
93
|
end
|
98
94
|
end
|
data/lib/mcollective/rpc.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "pp"
|
2
2
|
|
3
3
|
module MCollective
|
4
4
|
# Toolset to create a standard interface of client and agent using
|
@@ -21,15 +21,13 @@ module MCollective
|
|
21
21
|
def rpcoptions
|
22
22
|
oparser = MCollective::Optionparser.new({:verbose => false, :progress_bar => true}, "filter")
|
23
23
|
|
24
|
-
options = oparser.parse do |parser,
|
25
|
-
if block_given?
|
26
|
-
yield(parser, options)
|
27
|
-
end
|
24
|
+
options = oparser.parse do |parser, opts|
|
25
|
+
yield(parser, opts) if block_given?
|
28
26
|
|
29
|
-
Helpers.add_simplerpc_options(parser,
|
27
|
+
Helpers.add_simplerpc_options(parser, opts)
|
30
28
|
end
|
31
29
|
|
32
|
-
|
30
|
+
options
|
33
31
|
end
|
34
32
|
|
35
33
|
# Wrapper to create clients, supposed to be used as
|
@@ -57,7 +55,7 @@ module MCollective
|
|
57
55
|
# :exit_on_failure is true by default, and causes the application to
|
58
56
|
# exit if there is a failure constructing the RPC client. Set this flag
|
59
57
|
# to false to cause an Exception to be raised instead.
|
60
|
-
def rpcclient(agent, flags
|
58
|
+
def rpcclient(agent, flags={})
|
61
59
|
configfile = flags[:configfile] || Util.config_file_for_user
|
62
60
|
options = flags[:options] || nil
|
63
61
|
|
@@ -71,12 +69,12 @@ module MCollective
|
|
71
69
|
begin
|
72
70
|
if options
|
73
71
|
rpc = Client.new(agent, :configfile => options[:config], :options => options)
|
74
|
-
@options = rpc.options
|
75
72
|
else
|
76
73
|
rpc = Client.new(agent, :configfile => configfile)
|
77
|
-
@options = rpc.options
|
78
74
|
end
|
79
|
-
|
75
|
+
|
76
|
+
@options = rpc.options
|
77
|
+
rescue Exception => e # rubocop:disable Lint/RescueException:
|
80
78
|
if exit_on_failure
|
81
79
|
puts("Could not create RPC client: #{e}")
|
82
80
|
exit!
|
@@ -97,7 +95,7 @@ module MCollective
|
|
97
95
|
# printrpcstats can easily get access to it without
|
98
96
|
# users having to pass it around in params.
|
99
97
|
def self.stats(stats)
|
100
|
-
@@stats = stats
|
98
|
+
@@stats = stats # rubocop:disable Style/ClassVars
|
101
99
|
end
|
102
100
|
|
103
101
|
# means for other classes to drop discovered hosts into this module
|
@@ -105,7 +103,7 @@ module MCollective
|
|
105
103
|
# printrpcstats can easily get access to it without
|
106
104
|
# users having to pass it around in params.
|
107
105
|
def self.discovered(discovered)
|
108
|
-
@@discovered = discovered
|
106
|
+
@@discovered = discovered # rubocop:disable Style/ClassVars
|
109
107
|
end
|
110
108
|
|
111
109
|
# Prints stats, requires stats to be saved from elsewhere
|
@@ -125,7 +123,7 @@ module MCollective
|
|
125
123
|
|
126
124
|
flags = {:summarize => false, :caption => "rpc stats"}.merge(flags)
|
127
125
|
|
128
|
-
verbose =
|
126
|
+
verbose = !!@options[:verbose]
|
129
127
|
|
130
128
|
begin
|
131
129
|
stats = @@stats
|
@@ -143,17 +141,17 @@ module MCollective
|
|
143
141
|
# that produce an error will be printed
|
144
142
|
#
|
145
143
|
# To get details of each result run with the -v command line option.
|
146
|
-
def printrpc(result, flags
|
147
|
-
verbose =
|
144
|
+
def printrpc(result, flags={})
|
145
|
+
verbose = !!@options[:verbose]
|
148
146
|
verbose = flags[:verbose] || verbose
|
149
147
|
flatten = flags[:flatten] || false
|
150
148
|
format = @options[:output_format]
|
151
149
|
forced_mode = @options[:force_display_mode] || false
|
152
150
|
|
153
|
-
result_text =
|
151
|
+
result_text = Helpers.rpcresults(result, :verbose => verbose, :flatten => flatten, :format => format, :force_display_mode => forced_mode)
|
154
152
|
|
155
153
|
if result.is_a?(Array) && format == :console
|
156
|
-
puts "\n%s\n" % [
|
154
|
+
puts "\n%s\n" % [result_text]
|
157
155
|
else
|
158
156
|
# when we get just one result to print dont pad them all with
|
159
157
|
# blank spaces etc, just print the individual result with no
|
@@ -60,27 +60,31 @@ module MCollective
|
|
60
60
|
|
61
61
|
# Returns a hash with all stats
|
62
62
|
def to_hash
|
63
|
-
stats = {
|
63
|
+
stats = {
|
64
|
+
:validated => @validated,
|
64
65
|
:unvalidated => @unvalidated,
|
65
66
|
:passed => @passed,
|
66
67
|
:filtered => @filtered,
|
67
68
|
:starttime => @starttime,
|
68
69
|
:total => @total,
|
69
70
|
:ttlexpired => @ttlexpired,
|
70
|
-
:replies => @replies
|
71
|
+
:replies => @replies
|
72
|
+
}
|
71
73
|
|
72
|
-
reply = {
|
74
|
+
reply = {
|
75
|
+
:stats => stats,
|
73
76
|
:threads => [],
|
74
77
|
:pid => Process.pid,
|
75
|
-
:times => {}
|
78
|
+
:times => {}
|
79
|
+
}
|
76
80
|
|
77
|
-
::Process.times.each_pair{|k,v|
|
81
|
+
::Process.times.each_pair {|k, v|
|
78
82
|
k = k.to_sym
|
79
83
|
reply[:times][k] = v
|
80
84
|
}
|
81
85
|
|
82
86
|
Thread.list.each do |t|
|
83
|
-
reply[:threads] <<
|
87
|
+
reply[:threads] << t.inspect.to_s
|
84
88
|
end
|
85
89
|
|
86
90
|
reply[:agents] = Agents.agentlist
|
data/lib/mcollective/shell.rb
CHANGED
@@ -36,33 +36,33 @@ module MCollective
|
|
36
36
|
|
37
37
|
options.each do |opt, val|
|
38
38
|
case opt.to_s
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
when "stdout"
|
40
|
+
raise "stdout should support <<" unless val.respond_to?("<<")
|
41
|
+
@stdout = val
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
|
43
|
+
when "stderr"
|
44
|
+
raise "stderr should support <<" unless val.respond_to?("<<")
|
45
|
+
@stderr = val
|
46
46
|
|
47
|
-
|
48
|
-
|
49
|
-
|
47
|
+
when "stdin"
|
48
|
+
raise "stdin should be a String" unless val.is_a?(String)
|
49
|
+
@stdin = val
|
50
50
|
|
51
|
-
|
52
|
-
|
53
|
-
|
51
|
+
when "cwd"
|
52
|
+
raise "Directory #{val} does not exist" unless File.directory?(val)
|
53
|
+
@cwd = val
|
54
54
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
55
|
+
when "environment"
|
56
|
+
if val.nil?
|
57
|
+
@environment = {}
|
58
|
+
else
|
59
|
+
@environment.merge!(val.dup)
|
60
|
+
@environment = @environment.delete_if { |_k, v| v.nil? }
|
61
|
+
end
|
62
62
|
|
63
|
-
|
64
|
-
|
65
|
-
|
63
|
+
when "timeout"
|
64
|
+
raise "timeout should be a positive integer or the symbol :on_thread_exit symbol" unless val.eql?(:on_thread_exit) || (val.is_a?(Integer) && val > 0)
|
65
|
+
@timeout = val
|
66
66
|
end
|
67
67
|
end
|
68
68
|
end
|
@@ -76,7 +76,6 @@ module MCollective
|
|
76
76
|
|
77
77
|
opts["stdin"] = @stdin if @stdin
|
78
78
|
|
79
|
-
|
80
79
|
thread = Thread.current
|
81
80
|
# Start a double fork and exec with systemu which implies a guard thread.
|
82
81
|
# If a valid timeout is configured the guard thread will terminate the
|
@@ -90,32 +89,30 @@ module MCollective
|
|
90
89
|
sleep timeout
|
91
90
|
else
|
92
91
|
# sleep while the agent thread is still alive
|
93
|
-
while
|
94
|
-
sleep 0.1
|
95
|
-
end
|
92
|
+
sleep 0.1 while thread.alive?
|
96
93
|
end
|
97
94
|
|
98
95
|
# if the process is still running
|
99
|
-
if
|
96
|
+
if Process.kill(0, cid)
|
100
97
|
# and a timeout was specified
|
101
98
|
if timeout
|
102
99
|
if Util.windows?
|
103
|
-
Process.kill(
|
100
|
+
Process.kill("KILL", cid)
|
104
101
|
else
|
105
102
|
# Kill the process
|
106
|
-
Process.kill(
|
103
|
+
Process.kill("TERM", cid)
|
107
104
|
sleep 2
|
108
|
-
Process.kill(
|
105
|
+
Process.kill("KILL", cid) if Process.kill(0, cid) # rubocop:disable Metrics/BlockNesting
|
109
106
|
end
|
110
107
|
end
|
111
108
|
# only wait if the parent thread is dead
|
112
109
|
Process.waitpid(cid) unless thread.alive?
|
113
110
|
end
|
114
|
-
rescue SystemExit
|
115
|
-
rescue Errno::ESRCH
|
111
|
+
rescue SystemExit # rubocop:disable Lint/HandleExceptions
|
112
|
+
rescue Errno::ESRCH # rubocop:disable Lint/HandleExceptions
|
116
113
|
rescue Errno::ECHILD
|
117
114
|
Log.warn("Could not reap process '#{cid}'.")
|
118
|
-
rescue Exception => e
|
115
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
119
116
|
Log.info("Unexpected exception received while waiting for child process: #{e.class}: #{e}")
|
120
117
|
end
|
121
118
|
end
|
data/lib/mcollective/ssl.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "openssl"
|
2
|
+
require "base64"
|
3
|
+
require "digest/sha1"
|
4
4
|
|
5
5
|
module MCollective
|
6
6
|
# A class that assists in encrypting and decrypting data using a
|
@@ -161,7 +161,7 @@ module MCollective
|
|
161
161
|
cipher.decrypt
|
162
162
|
cipher.key = key
|
163
163
|
cipher.pkcs5_keyivgen(key)
|
164
|
-
|
164
|
+
cipher.update(crypt_string) + cipher.final
|
165
165
|
end
|
166
166
|
|
167
167
|
# Signs a string using the private key
|
@@ -195,9 +195,7 @@ module MCollective
|
|
195
195
|
def self.base64_decode(string)
|
196
196
|
# The Base 64 character set is A-Z a-z 0-9 + / =
|
197
197
|
# Also allow for whitespace, but raise if we get anything else
|
198
|
-
if string !~ /^[A-Za-z0-9+\/=\s]+$/
|
199
|
-
raise ArgumentError, 'invalid base64'
|
200
|
-
end
|
198
|
+
raise(ArgumentError, "invalid base64") if string !~ /^[A-Za-z0-9+\/=\s]+$/
|
201
199
|
Base64.decode64(string)
|
202
200
|
end
|
203
201
|
|
@@ -216,9 +214,9 @@ module MCollective
|
|
216
214
|
# https://github.com/kwilczynski/puppet-functions/blob/master/lib/puppet/parser/functions/uuid.rb
|
217
215
|
#
|
218
216
|
def self.uuid(string=nil)
|
219
|
-
string ||= OpenSSL::Random.random_bytes(16).unpack(
|
217
|
+
string ||= OpenSSL::Random.random_bytes(16).unpack("H*").shift
|
220
218
|
|
221
|
-
uuid_name_space_dns = [0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8].map
|
219
|
+
uuid_name_space_dns = [0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8].map(&:chr).join
|
222
220
|
|
223
221
|
sha1 = Digest::SHA1.new
|
224
222
|
sha1.update(uuid_name_space_dns)
|
@@ -236,10 +234,10 @@ module MCollective
|
|
236
234
|
bytes[8] |= 0x80
|
237
235
|
|
238
236
|
bytes = [4, 2, 2, 2, 6].collect do |i|
|
239
|
-
bytes.slice!(0, i).pack(
|
237
|
+
bytes.slice!(0, i).pack("C*").unpack("H*")
|
240
238
|
end
|
241
239
|
|
242
|
-
bytes.join(
|
240
|
+
bytes.join("-")
|
243
241
|
end
|
244
242
|
|
245
243
|
# Reads either a :public or :private key from disk, uses an
|
@@ -280,6 +278,5 @@ module MCollective
|
|
280
278
|
raise "Can only load :public or :private keys"
|
281
279
|
end
|
282
280
|
end
|
283
|
-
|
284
281
|
end
|
285
282
|
end
|
data/lib/mcollective/util.rb
CHANGED
@@ -6,10 +6,10 @@ module MCollective
|
|
6
6
|
# If the passed name starts with a / it's assumed to be regex
|
7
7
|
# and will use regex to match
|
8
8
|
def self.has_agent?(agent)
|
9
|
-
agent = Regexp.new(agent.gsub("\/", "")) if agent.
|
9
|
+
agent = Regexp.new(agent.gsub("\/", "")) if agent.start_with?("/")
|
10
10
|
|
11
11
|
if agent.is_a?(Regexp)
|
12
|
-
if Agents.agentlist.grep(agent).
|
12
|
+
if !Agents.agentlist.grep(agent).empty?
|
13
13
|
return true
|
14
14
|
else
|
15
15
|
return false
|
@@ -17,8 +17,6 @@ module MCollective
|
|
17
17
|
else
|
18
18
|
return Agents.agentlist.include?(agent)
|
19
19
|
end
|
20
|
-
|
21
|
-
false
|
22
20
|
end
|
23
21
|
|
24
22
|
# On windows ^c can't interrupt the VM if its blocking on
|
@@ -36,7 +34,7 @@ module MCollective
|
|
36
34
|
# If the passed name starts with a / it's assumed to be regex
|
37
35
|
# and will use regex to match
|
38
36
|
def self.has_cf_class?(klass)
|
39
|
-
klass = Regexp.new(klass.gsub("\/", "")) if klass.
|
37
|
+
klass = Regexp.new(klass.gsub("\/", "")) if klass.start_with?("/")
|
40
38
|
cfile = Config.instance.classesfile
|
41
39
|
|
42
40
|
Log.debug("Looking for configuration management classes in #{cfile}")
|
@@ -45,11 +43,11 @@ module MCollective
|
|
45
43
|
File.readlines(cfile).each do |k|
|
46
44
|
if klass.is_a?(Regexp)
|
47
45
|
return true if k.chomp.match(klass)
|
48
|
-
|
49
|
-
return true
|
46
|
+
elsif k.chomp == klass
|
47
|
+
return true
|
50
48
|
end
|
51
49
|
end
|
52
|
-
rescue Exception => e
|
50
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
53
51
|
Log.warn("Parsing classes file '#{cfile}' failed: #{e.class}: #{e}")
|
54
52
|
end
|
55
53
|
|
@@ -67,7 +65,6 @@ module MCollective
|
|
67
65
|
# If the passed value starts with a / it's assumed to be regex
|
68
66
|
# and will use regex to match
|
69
67
|
def self.has_fact?(fact, value, operator)
|
70
|
-
|
71
68
|
Log.debug("Comparing #{fact} #{operator} #{value}")
|
72
69
|
Log.debug("where :fact = '#{fact}', :operator = '#{operator}', :value = '#{value}'")
|
73
70
|
|
@@ -86,30 +83,28 @@ module MCollective
|
|
86
83
|
end
|
87
84
|
|
88
85
|
def self.test_fact_value(fact, value, operator)
|
89
|
-
if operator ==
|
86
|
+
if operator == "=~"
|
90
87
|
# to maintain backward compat we send the value
|
91
88
|
# as /.../ which is what 1.0.x needed. this strips
|
92
89
|
# off the /'s which is what we need here
|
93
|
-
if value =~ /^\/(.+)\/$/
|
94
|
-
value = $1
|
95
|
-
end
|
90
|
+
value = $1 if value =~ /^\/(.+)\/$/
|
96
91
|
|
97
92
|
return true if fact.match(Regexp.new(value))
|
98
93
|
|
99
94
|
elsif operator == "=="
|
100
95
|
return true if fact == value
|
101
96
|
|
102
|
-
elsif [
|
97
|
+
elsif ["<=", ">=", "<", ">", "!="].include?(operator)
|
103
98
|
# Yuk - need to type cast, but to_i and to_f are overzealous
|
104
99
|
if value =~ /^[0-9]+$/ && fact =~ /^[0-9]+$/
|
105
|
-
fact = Integer(fact)
|
106
|
-
value = Integer(value)
|
100
|
+
fact = Integer(fact) # rubocop:disable Lint/UselessAssignment
|
101
|
+
value = Integer(value) # rubocop:disable Lint/UselessAssignment
|
107
102
|
elsif value =~ /^[0-9]+.[0-9]+$/ && fact =~ /^[0-9]+.[0-9]+$/
|
108
|
-
fact = Float(fact)
|
109
|
-
value = Float(value)
|
103
|
+
fact = Float(fact) # rubocop:disable Lint/UselessAssignment
|
104
|
+
value = Float(value) # rubocop:disable Lint/UselessAssignment
|
110
105
|
end
|
111
106
|
|
112
|
-
return true if eval("fact #{operator} value")
|
107
|
+
return true if eval("fact #{operator} value") # rubocop:disable Security/Eval
|
113
108
|
end
|
114
109
|
|
115
110
|
false
|
@@ -121,12 +116,12 @@ module MCollective
|
|
121
116
|
# If the passed name starts with a / it's assumed to be regex
|
122
117
|
# and will use regex to match
|
123
118
|
def self.has_identity?(identity)
|
124
|
-
identity = Regexp.new(identity.gsub("\/", "")) if identity.
|
119
|
+
identity = Regexp.new(identity.gsub("\/", "")) if identity.start_with?("/")
|
125
120
|
|
126
121
|
if identity.is_a?(Regexp)
|
127
122
|
return Config.instance.identity.match(identity)
|
128
|
-
|
129
|
-
return true
|
123
|
+
elsif Config.instance.identity == identity
|
124
|
+
return true
|
130
125
|
end
|
131
126
|
|
132
127
|
false
|
@@ -139,56 +134,94 @@ module MCollective
|
|
139
134
|
|
140
135
|
# Creates an empty filter
|
141
136
|
def self.empty_filter
|
142
|
-
{
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
137
|
+
{
|
138
|
+
"fact" => [],
|
139
|
+
"cf_class" => [],
|
140
|
+
"agent" => [],
|
141
|
+
"identity" => [],
|
142
|
+
"compound" => []
|
143
|
+
}
|
147
144
|
end
|
148
145
|
|
149
146
|
# Returns the PuppetLabs mcollective path for windows
|
150
147
|
def self.windows_prefix
|
151
|
-
require
|
152
|
-
|
148
|
+
require "win32/dir"
|
149
|
+
File.join(Dir::COMMON_APPDATA, "PuppetLabs", "mcollective")
|
153
150
|
end
|
154
151
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
152
|
+
def self.choria_windows_prefix
|
153
|
+
require "win32/dir"
|
154
|
+
File.join(Dir::COMMON_APPDATA, "ChoriaIO", "choria")
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.mcollective_config_paths_for_user
|
159
158
|
config_paths = []
|
160
159
|
|
161
|
-
# user dotfile
|
162
160
|
begin
|
163
161
|
# File.expand_path will raise if HOME isn't set, catch it
|
164
162
|
user_path = File.expand_path("~/.mcollective")
|
165
163
|
config_paths << user_path
|
166
|
-
rescue Exception
|
164
|
+
rescue Exception # rubocop:disable Lint/RescueException, Lint/HandleExceptions
|
167
165
|
end
|
168
166
|
|
169
|
-
|
170
|
-
|
171
|
-
config_paths << File.join(self.windows_prefix, 'etc', 'client.cfg')
|
167
|
+
if windows?
|
168
|
+
config_paths << File.join(windows_prefix, "etc", "client.cfg")
|
172
169
|
else
|
173
|
-
config_paths <<
|
174
|
-
config_paths <<
|
170
|
+
config_paths << "/etc/puppetlabs/mcollective/client.cfg"
|
171
|
+
config_paths << "/etc/mcollective/client.cfg"
|
175
172
|
end
|
176
173
|
|
177
|
-
|
174
|
+
config_paths
|
175
|
+
end
|
176
|
+
|
177
|
+
def self.choria_config_paths_for_user
|
178
|
+
config_paths = []
|
179
|
+
|
180
|
+
begin
|
181
|
+
# File.expand_path will raise if HOME isn't set, catch it
|
182
|
+
user_path = File.expand_path("~/.choriarc")
|
183
|
+
config_paths << user_path
|
184
|
+
rescue Exception # rubocop:disable Lint/RescueException, Lint/HandleExceptions
|
185
|
+
end
|
186
|
+
|
187
|
+
if windows?
|
188
|
+
config_paths << File.join(choria_windows_prefix, "etc", "client.conf")
|
189
|
+
else
|
190
|
+
config_paths << "/etc/choria/client.conf"
|
191
|
+
end
|
192
|
+
|
193
|
+
config_paths
|
194
|
+
end
|
195
|
+
|
196
|
+
# Picks the default user config file, pririties are first Choria ones then old MCollective ones
|
197
|
+
#
|
198
|
+
# In roughly this order, first to exist is used:
|
199
|
+
#
|
200
|
+
# - ~/.choriarc
|
201
|
+
# - APPData/ChoriaIO/choria/etc/client.conf on windows
|
202
|
+
# - /etc/choria/client.conf on unix
|
203
|
+
# - ~/.mcollective
|
204
|
+
# - APPData/PuppetLabs/mcollective/etc/client.cfg on windows
|
205
|
+
# - /etc/puppetlabs/mcollective/client.cfg
|
206
|
+
# - /etc/mcollective/client.cfg
|
207
|
+
def self.config_file_for_user
|
208
|
+
config_paths = choria_config_paths_for_user + mcollective_config_paths_for_user
|
178
209
|
found = config_paths.find_index { |file| File.readable?(file) } || 0
|
179
|
-
|
210
|
+
config_paths[found]
|
180
211
|
end
|
181
212
|
|
182
213
|
# Creates a standard options hash
|
183
214
|
def self.default_options
|
184
|
-
{
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
215
|
+
{
|
216
|
+
:verbose => false,
|
217
|
+
:disctimeout => nil,
|
218
|
+
:timeout => 5,
|
219
|
+
:config => config_file_for_user,
|
220
|
+
:collective => nil,
|
221
|
+
:discovery_method => nil,
|
222
|
+
:discovery_options => Config.instance.default_discovery_options,
|
223
|
+
:filter => empty_filter
|
224
|
+
}
|
192
225
|
end
|
193
226
|
|
194
227
|
def self.make_subscriptions(agent, type, collective=nil)
|
@@ -237,15 +270,15 @@ module MCollective
|
|
237
270
|
# Parse a fact filter string like foo=bar into the tuple hash thats needed
|
238
271
|
def self.parse_fact_string(fact)
|
239
272
|
if fact =~ /^([^ ]+?)[ ]*=>[ ]*(.+)/
|
240
|
-
|
273
|
+
{:fact => $1, :value => $2, :operator => ">="}
|
241
274
|
elsif fact =~ /^([^ ]+?)[ ]*=<[ ]*(.+)/
|
242
|
-
|
275
|
+
{:fact => $1, :value => $2, :operator => "<="}
|
243
276
|
elsif fact =~ /^([^ ]+?)[ ]*(<=|>=|<|>|!=|==|=~)[ ]*(.+)/
|
244
|
-
|
277
|
+
{:fact => $1, :value => $3, :operator => $2}
|
245
278
|
elsif fact =~ /^(.+?)[ ]*=[ ]*\/(.+)\/$/
|
246
|
-
|
279
|
+
{:fact => $1, :value => "/#{$2}/", :operator => "=~"}
|
247
280
|
elsif fact =~ /^([^= ]+?)[ ]*=[ ]*(.+)/
|
248
|
-
|
281
|
+
{:fact => $1, :value => $2, :operator => "=="}
|
249
282
|
else
|
250
283
|
raise "Could not parse fact #{fact} it does not appear to be in a valid format"
|
251
284
|
end
|
@@ -267,11 +300,11 @@ module MCollective
|
|
267
300
|
# combo is regarded as line continuation and simply ignored.
|
268
301
|
str.gsub!(/\n/, "'\n'")
|
269
302
|
|
270
|
-
|
303
|
+
str
|
271
304
|
end
|
272
305
|
|
273
306
|
def self.windows?
|
274
|
-
!!(RbConfig::CONFIG[
|
307
|
+
!!(RbConfig::CONFIG["host_os"] =~ /mswin|win32|dos|mingw|cygwin/i)
|
275
308
|
end
|
276
309
|
|
277
310
|
# Return color codes, if the config color= option is false
|
@@ -279,12 +312,14 @@ module MCollective
|
|
279
312
|
def self.color(code)
|
280
313
|
colorize = Config.instance.color
|
281
314
|
|
282
|
-
colors = {
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
315
|
+
colors = {
|
316
|
+
:red => "[31m",
|
317
|
+
:green => "[32m",
|
318
|
+
:yellow => "[33m",
|
319
|
+
:cyan => "[36m",
|
320
|
+
:bold => "[1m",
|
321
|
+
:reset => "[0m"
|
322
|
+
}
|
288
323
|
|
289
324
|
if colorize
|
290
325
|
return colors[code] || ""
|
@@ -295,7 +330,7 @@ module MCollective
|
|
295
330
|
|
296
331
|
# Helper to return a string in specific color
|
297
332
|
def self.colorize(code, msg)
|
298
|
-
"%s%s%s" % [
|
333
|
+
"%s%s%s" % [color(code), msg, color(:reset)]
|
299
334
|
end
|
300
335
|
|
301
336
|
# Returns the current ruby version as per RUBY_VERSION, mostly
|
@@ -316,7 +351,7 @@ module MCollective
|
|
316
351
|
# The terminal size is detected by default, but custom line widths can
|
317
352
|
# passed. All strings will also be left aligned with 5 whitespace characters
|
318
353
|
# by default.
|
319
|
-
def self.align_text(text, console_cols
|
354
|
+
def self.align_text(text, console_cols=nil, preamble=5)
|
320
355
|
unless console_cols
|
321
356
|
console_cols = terminal_dimensions[0]
|
322
357
|
|
@@ -334,40 +369,36 @@ module MCollective
|
|
334
369
|
console_cols = 80 if console_cols <= 0
|
335
370
|
|
336
371
|
text = text.split("\n")
|
337
|
-
piece =
|
372
|
+
piece = ""
|
338
373
|
whitespace = 0
|
339
374
|
|
340
375
|
text.each_with_index do |line, i|
|
341
376
|
whitespace = 0
|
342
377
|
|
343
|
-
while whitespace < line.length && line[whitespace].chr ==
|
378
|
+
while whitespace < line.length && line[whitespace].chr == " "
|
344
379
|
whitespace += 1
|
345
380
|
end
|
346
381
|
|
347
382
|
# If the current line is empty, indent it so that a snippet
|
348
383
|
# from the previous line is aligned correctly.
|
349
|
-
if line == ""
|
350
|
-
line = (" " * whitespace)
|
351
|
-
end
|
384
|
+
line = (" " * whitespace) if line == ""
|
352
385
|
|
353
386
|
# If text was snipped from the previous line, prepend it to the
|
354
387
|
# current line after any current indentation.
|
355
|
-
if piece !=
|
388
|
+
if piece != ""
|
356
389
|
# Reset whitespaces to 0 if there are more whitespaces than there are
|
357
390
|
# console columns
|
358
391
|
whitespace = 0 if whitespace >= console_cols
|
359
392
|
|
360
393
|
# If the current line is empty and being prepended to, create a new
|
361
394
|
# empty line in the text so that formatting is preserved.
|
362
|
-
if text[i + 1] && line == (" " * whitespace)
|
363
|
-
text.insert(i + 1, "")
|
364
|
-
end
|
395
|
+
text.insert(i + 1, "") if text[i + 1] && line == (" " * whitespace)
|
365
396
|
|
366
397
|
# Add the snipped text to the current line
|
367
398
|
line.insert(whitespace, "#{piece} ")
|
368
399
|
end
|
369
400
|
|
370
|
-
piece =
|
401
|
+
piece = ""
|
371
402
|
|
372
403
|
# Compare the line length to the allowed line length.
|
373
404
|
# If it exceeds it, snip the offending text from the line
|
@@ -375,9 +406,7 @@ module MCollective
|
|
375
406
|
if line.length > (console_cols + preamble)
|
376
407
|
reverse = console_cols
|
377
408
|
|
378
|
-
while line[reverse].chr !=
|
379
|
-
reverse -= 1
|
380
|
-
end
|
409
|
+
reverse -= 1 while line[reverse].chr != " "
|
381
410
|
|
382
411
|
piece = line.slice!(reverse, (line.length - 1)).lstrip
|
383
412
|
end
|
@@ -385,13 +414,13 @@ module MCollective
|
|
385
414
|
# If a snippet exists when all the columns in the text have been
|
386
415
|
# updated, create a new line and append the snippet to it, using
|
387
416
|
# the same left alignment as the last line in the text.
|
388
|
-
if piece !=
|
389
|
-
text[i+1] = "#{' ' *
|
390
|
-
piece =
|
417
|
+
if piece != "" && text[i + 1].nil?
|
418
|
+
text[i + 1] = "#{' ' * whitespace}#{piece}"
|
419
|
+
piece = ""
|
391
420
|
end
|
392
421
|
|
393
422
|
# Add the preamble to the line and add it to the text
|
394
|
-
line = ((
|
423
|
+
line = ((" " * preamble) + line)
|
395
424
|
text[i] = line
|
396
425
|
end
|
397
426
|
|
@@ -402,7 +431,7 @@ module MCollective
|
|
402
431
|
#
|
403
432
|
# Returns [0, 0] if it can't figure it out or if you're
|
404
433
|
# not running on a tty
|
405
|
-
def self.terminal_dimensions(stdout
|
434
|
+
def self.terminal_dimensions(stdout=STDOUT, environment=ENV)
|
406
435
|
return [0, 0] unless stdout.tty?
|
407
436
|
|
408
437
|
return [80, 40] if Util.windows?
|
@@ -413,8 +442,8 @@ module MCollective
|
|
413
442
|
elsif environment["TERM"] && command_in_path?("tput")
|
414
443
|
return [`tput cols`.to_i, `tput lines`.to_i]
|
415
444
|
|
416
|
-
elsif command_in_path?(
|
417
|
-
return `stty size`.scan(/\d+/).map
|
445
|
+
elsif command_in_path?("stty")
|
446
|
+
return `stty size`.scan(/\d+/).map(&:to_i)
|
418
447
|
else
|
419
448
|
return [0, 0]
|
420
449
|
end
|
@@ -444,28 +473,26 @@ module MCollective
|
|
444
473
|
ax = version_a.scan(vre)
|
445
474
|
bx = version_b.scan(vre)
|
446
475
|
|
447
|
-
while
|
476
|
+
while !ax.empty? && !bx.empty?
|
448
477
|
a = ax.shift
|
449
478
|
b = bx.shift
|
450
479
|
|
451
|
-
if
|
452
|
-
elsif
|
453
|
-
elsif
|
454
|
-
elsif
|
455
|
-
elsif
|
456
|
-
elsif
|
457
|
-
elsif
|
458
|
-
elsif
|
459
|
-
if
|
460
|
-
return a.to_s.upcase <=> b.to_s.upcase
|
461
|
-
end
|
480
|
+
if a == b then next
|
481
|
+
elsif a == "-" && b == "-" then next
|
482
|
+
elsif a == "-" then return -1
|
483
|
+
elsif b == "-" then return 1
|
484
|
+
elsif a == "." && b == "." then next
|
485
|
+
elsif a == "." then return -1
|
486
|
+
elsif b == "." then return 1
|
487
|
+
elsif a =~ /^\d+$/ && b =~ /^\d+$/
|
488
|
+
return a.to_s.upcase <=> b.to_s.upcase if a =~ /^0/ || b =~ /^0/
|
462
489
|
return a.to_i <=> b.to_i
|
463
490
|
else
|
464
491
|
return a.upcase <=> b.upcase
|
465
492
|
end
|
466
493
|
end
|
467
494
|
|
468
|
-
version_a <=> version_b
|
495
|
+
version_a <=> version_b
|
469
496
|
end
|
470
497
|
|
471
498
|
# we should really use Pathname#absolute? but it's not in all the
|
@@ -486,7 +513,7 @@ module MCollective
|
|
486
513
|
def self.str_to_bool(val)
|
487
514
|
clean_val = val.to_s.strip
|
488
515
|
if clean_val =~ /^(1|yes|true|y|t)$/i
|
489
|
-
return
|
516
|
+
return true
|
490
517
|
elsif clean_val =~ /^(0|no|false|n|f)$/i
|
491
518
|
return false
|
492
519
|
else
|
@@ -498,10 +525,10 @@ module MCollective
|
|
498
525
|
def self.templatepath(template_file)
|
499
526
|
config_dir = File.dirname(Config.instance.configfile)
|
500
527
|
template_path = File.join(config_dir, template_file)
|
501
|
-
return template_path if File.
|
528
|
+
return template_path if File.exist?(template_path)
|
502
529
|
|
503
530
|
template_path = File.join("/etc/mcollective", template_file)
|
504
|
-
|
531
|
+
template_path
|
505
532
|
end
|
506
533
|
|
507
534
|
# subscribe to the direct addressing queue
|
@@ -511,68 +538,66 @@ module MCollective
|
|
511
538
|
|
512
539
|
# Get field size for printing
|
513
540
|
def self.field_size(elements, min_size=40)
|
514
|
-
max_length = elements.max_by
|
541
|
+
max_length = elements.max_by(&:length).length
|
515
542
|
max_length > min_size ? max_length : min_size
|
516
543
|
end
|
517
544
|
|
518
545
|
# Calculate number of fields for printing
|
519
546
|
def self.field_number(field_size, max_size=90)
|
520
|
-
number = (max_size/field_size).to_i
|
521
|
-
|
547
|
+
number = (max_size / field_size).to_i
|
548
|
+
number == 0 ? 1 : number
|
522
549
|
end
|
523
|
-
|
524
|
-
def self.get_hidden_input_on_windows
|
525
|
-
require
|
550
|
+
|
551
|
+
def self.get_hidden_input_on_windows # rubocop:disable Naming/AccessorMethodName
|
552
|
+
require "Win32API"
|
553
|
+
|
526
554
|
# Hook into getch from crtdll. Keep reading all keys till return
|
527
555
|
# or newline is hit.
|
528
556
|
# If key is backspace or delete, then delete the character and update
|
529
557
|
# the buffer.
|
530
|
-
input =
|
531
|
-
|
532
|
-
|
533
|
-
if
|
534
|
-
|
535
|
-
|
536
|
-
end
|
558
|
+
input = ""
|
559
|
+
|
560
|
+
while char = Win32API.new("crtdll", "_getch", [], "I").Call
|
561
|
+
break if [10, 13].include?(char) # return or newline
|
562
|
+
if [127, 8].include?(char) # backspace and delete
|
563
|
+
input.slice!(-1, 1) unless input.empty?
|
537
564
|
else
|
538
565
|
input << char.chr
|
539
566
|
end
|
540
567
|
end
|
541
|
-
|
568
|
+
|
542
569
|
input
|
543
570
|
end
|
544
571
|
|
545
|
-
def self.get_hidden_input_on_unix
|
572
|
+
def self.get_hidden_input_on_unix # rubocop:disable Naming/AccessorMethodName
|
546
573
|
unless $stdin.tty?
|
547
|
-
raise
|
574
|
+
raise "Could not hook to stdin to hide input. If using SSH, try using -t flag while connecting to server."
|
548
575
|
end
|
549
|
-
unless system
|
550
|
-
raise
|
576
|
+
unless system "stty -echo -icanon"
|
577
|
+
raise "Could not hide input using stty command."
|
551
578
|
end
|
552
579
|
input = $stdin.gets
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
580
|
+
ensure
|
581
|
+
unless system "stty echo icanon"
|
582
|
+
raise "Could not enable echoing of input. Try executing `stty echo icanon` to debug."
|
583
|
+
end
|
557
584
|
input
|
558
585
|
end
|
559
586
|
|
560
|
-
def self.get_hidden_input(message=
|
561
|
-
unless message.nil?
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
require 'io/console'
|
587
|
+
def self.get_hidden_input(message="Please enter data: ")
|
588
|
+
print message unless message.nil?
|
589
|
+
|
590
|
+
if versioncmp(ruby_version, "1.9.3") >= 0
|
591
|
+
require "io/console"
|
566
592
|
input = $stdin.noecho(&:gets)
|
593
|
+
elsif windows? # Use hacks to get hidden input on Ruby <1.9.3
|
594
|
+
input = get_hidden_input_on_windows
|
567
595
|
else
|
568
|
-
|
569
|
-
if self.windows?
|
570
|
-
input = self.get_hidden_input_on_windows()
|
571
|
-
else
|
572
|
-
input = self.get_hidden_input_on_unix()
|
573
|
-
end
|
596
|
+
input = get_hidden_input_on_unix
|
574
597
|
end
|
598
|
+
|
575
599
|
input.chomp! if input
|
600
|
+
|
576
601
|
input
|
577
602
|
end
|
578
603
|
end
|