choria-mcorpc-support 2.22.0 → 2.23.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mcollective.rb +1 -2
  3. data/lib/mcollective/agent/bolt_tasks.ddl +253 -0
  4. data/lib/mcollective/agent/bolt_tasks.json +365 -0
  5. data/lib/mcollective/agent/bolt_tasks.rb +178 -0
  6. data/lib/mcollective/agent/choria_util.ddl +152 -0
  7. data/lib/mcollective/agent/choria_util.json +244 -0
  8. data/lib/mcollective/agent/rpcutil.ddl +8 -4
  9. data/lib/mcollective/agent/rpcutil.json +333 -0
  10. data/lib/mcollective/agent/scout.ddl +169 -0
  11. data/lib/mcollective/agent/scout.json +224 -0
  12. data/lib/mcollective/agents.rb +7 -6
  13. data/lib/mcollective/aggregate.rb +4 -4
  14. data/lib/mcollective/aggregate/average.rb +2 -2
  15. data/lib/mcollective/aggregate/base.rb +2 -2
  16. data/lib/mcollective/aggregate/result.rb +3 -3
  17. data/lib/mcollective/aggregate/result/collection_result.rb +2 -2
  18. data/lib/mcollective/aggregate/result/numeric_result.rb +2 -2
  19. data/lib/mcollective/aggregate/sum.rb +2 -2
  20. data/lib/mcollective/aggregate/summary.rb +3 -4
  21. data/lib/mcollective/application.rb +57 -21
  22. data/lib/mcollective/application/choria.rb +189 -0
  23. data/lib/mcollective/application/completion.rb +6 -6
  24. data/lib/mcollective/application/facts.rb +11 -11
  25. data/lib/mcollective/application/federation.rb +237 -0
  26. data/lib/mcollective/application/find.rb +4 -4
  27. data/lib/mcollective/application/help.rb +3 -3
  28. data/lib/mcollective/application/inventory.rb +3 -341
  29. data/lib/mcollective/application/ping.rb +5 -51
  30. data/lib/mcollective/application/playbook.rb +207 -0
  31. data/lib/mcollective/application/plugin.rb +106 -106
  32. data/lib/mcollective/application/rpc.rb +3 -108
  33. data/lib/mcollective/application/tasks.rb +425 -0
  34. data/lib/mcollective/applications.rb +11 -10
  35. data/lib/mcollective/audit/choria.rb +33 -0
  36. data/lib/mcollective/cache.rb +2 -4
  37. data/lib/mcollective/client.rb +11 -10
  38. data/lib/mcollective/config.rb +21 -34
  39. data/lib/mcollective/connector/base.rb +2 -1
  40. data/lib/mcollective/connector/nats.ddl +9 -0
  41. data/lib/mcollective/connector/nats.rb +450 -0
  42. data/lib/mcollective/data.rb +8 -3
  43. data/lib/mcollective/data/agent_data.rb +1 -1
  44. data/lib/mcollective/data/base.rb +6 -5
  45. data/lib/mcollective/data/bolt_task_data.ddl +90 -0
  46. data/lib/mcollective/data/bolt_task_data.rb +32 -0
  47. data/lib/mcollective/data/collective_data.rb +1 -1
  48. data/lib/mcollective/data/fact_data.rb +6 -6
  49. data/lib/mcollective/data/fstat_data.rb +2 -4
  50. data/lib/mcollective/data/result.rb +7 -2
  51. data/lib/mcollective/ddl/agentddl.rb +5 -17
  52. data/lib/mcollective/ddl/base.rb +10 -13
  53. data/lib/mcollective/discovery.rb +24 -39
  54. data/lib/mcollective/discovery/choria.ddl +11 -0
  55. data/lib/mcollective/discovery/choria.rb +223 -0
  56. data/lib/mcollective/discovery/flatfile.rb +7 -8
  57. data/lib/mcollective/discovery/mc.rb +2 -2
  58. data/lib/mcollective/discovery/stdin.rb +17 -18
  59. data/lib/mcollective/exceptions.rb +13 -0
  60. data/lib/mcollective/facts/base.rb +9 -9
  61. data/lib/mcollective/facts/yaml_facts.rb +12 -12
  62. data/lib/mcollective/generators.rb +3 -3
  63. data/lib/mcollective/generators/agent_generator.rb +3 -4
  64. data/lib/mcollective/generators/base.rb +14 -15
  65. data/lib/mcollective/generators/data_generator.rb +5 -6
  66. data/lib/mcollective/log.rb +2 -2
  67. data/lib/mcollective/logger/base.rb +3 -2
  68. data/lib/mcollective/logger/console_logger.rb +10 -10
  69. data/lib/mcollective/logger/file_logger.rb +7 -7
  70. data/lib/mcollective/logger/syslog_logger.rb +11 -15
  71. data/lib/mcollective/message.rb +8 -39
  72. data/lib/mcollective/monkey_patches.rb +2 -4
  73. data/lib/mcollective/optionparser.rb +2 -1
  74. data/lib/mcollective/pluginmanager.rb +3 -5
  75. data/lib/mcollective/pluginpackager.rb +1 -3
  76. data/lib/mcollective/pluginpackager/agent_definition.rb +3 -8
  77. data/lib/mcollective/pluginpackager/forge_packager.rb +7 -9
  78. data/lib/mcollective/pluginpackager/standard_definition.rb +1 -2
  79. data/lib/mcollective/registration/base.rb +18 -16
  80. data/lib/mcollective/rpc.rb +2 -4
  81. data/lib/mcollective/rpc/actionrunner.rb +16 -18
  82. data/lib/mcollective/rpc/agent.rb +26 -43
  83. data/lib/mcollective/rpc/audit.rb +1 -0
  84. data/lib/mcollective/rpc/client.rb +67 -85
  85. data/lib/mcollective/rpc/helpers.rb +55 -62
  86. data/lib/mcollective/rpc/progress.rb +2 -2
  87. data/lib/mcollective/rpc/reply.rb +17 -19
  88. data/lib/mcollective/rpc/request.rb +7 -5
  89. data/lib/mcollective/rpc/result.rb +6 -8
  90. data/lib/mcollective/rpc/stats.rb +49 -58
  91. data/lib/mcollective/security/base.rb +13 -56
  92. data/lib/mcollective/security/choria.rb +765 -0
  93. data/lib/mcollective/shell.rb +9 -4
  94. data/lib/mcollective/signer/base.rb +28 -0
  95. data/lib/mcollective/signer/choria.rb +185 -0
  96. data/lib/mcollective/ssl.rb +8 -6
  97. data/lib/mcollective/util.rb +73 -82
  98. data/lib/mcollective/util/bolt_support.rb +176 -0
  99. data/lib/mcollective/util/bolt_support/plan_runner.rb +167 -0
  100. data/lib/mcollective/util/bolt_support/task_result.rb +94 -0
  101. data/lib/mcollective/util/bolt_support/task_results.rb +128 -0
  102. data/lib/mcollective/util/choria.rb +946 -0
  103. data/lib/mcollective/util/indifferent_hash.rb +12 -0
  104. data/lib/mcollective/util/natswrapper.rb +242 -0
  105. data/lib/mcollective/util/playbook.rb +435 -0
  106. data/lib/mcollective/util/playbook/data_stores.rb +201 -0
  107. data/lib/mcollective/util/playbook/data_stores/base.rb +99 -0
  108. data/lib/mcollective/util/playbook/data_stores/consul_data_store.rb +88 -0
  109. data/lib/mcollective/util/playbook/data_stores/environment_data_store.rb +33 -0
  110. data/lib/mcollective/util/playbook/data_stores/etcd_data_store.rb +42 -0
  111. data/lib/mcollective/util/playbook/data_stores/file_data_store.rb +106 -0
  112. data/lib/mcollective/util/playbook/data_stores/shell_data_store.rb +103 -0
  113. data/lib/mcollective/util/playbook/inputs.rb +265 -0
  114. data/lib/mcollective/util/playbook/nodes.rb +207 -0
  115. data/lib/mcollective/util/playbook/nodes/mcollective_nodes.rb +86 -0
  116. data/lib/mcollective/util/playbook/nodes/pql_nodes.rb +40 -0
  117. data/lib/mcollective/util/playbook/nodes/shell_nodes.rb +55 -0
  118. data/lib/mcollective/util/playbook/nodes/terraform_nodes.rb +65 -0
  119. data/lib/mcollective/util/playbook/nodes/yaml_nodes.rb +47 -0
  120. data/lib/mcollective/util/playbook/playbook_logger.rb +47 -0
  121. data/lib/mcollective/util/playbook/puppet_logger.rb +51 -0
  122. data/lib/mcollective/util/playbook/report.rb +152 -0
  123. data/lib/mcollective/util/playbook/task_result.rb +55 -0
  124. data/lib/mcollective/util/playbook/tasks.rb +196 -0
  125. data/lib/mcollective/util/playbook/tasks/base.rb +45 -0
  126. data/lib/mcollective/util/playbook/tasks/graphite_event_task.rb +64 -0
  127. data/lib/mcollective/util/playbook/tasks/mcollective_task.rb +356 -0
  128. data/lib/mcollective/util/playbook/tasks/shell_task.rb +93 -0
  129. data/lib/mcollective/util/playbook/tasks/slack_task.rb +105 -0
  130. data/lib/mcollective/util/playbook/tasks/webhook_task.rb +136 -0
  131. data/lib/mcollective/util/playbook/template_util.rb +98 -0
  132. data/lib/mcollective/util/playbook/uses.rb +169 -0
  133. data/lib/mcollective/util/tasks_support.rb +751 -0
  134. data/lib/mcollective/util/tasks_support/cli.rb +260 -0
  135. data/lib/mcollective/util/tasks_support/default_formatter.rb +138 -0
  136. data/lib/mcollective/util/tasks_support/json_formatter.rb +108 -0
  137. data/lib/mcollective/validator.rb +6 -1
  138. data/lib/mcollective/validator/bolt_task_name_validator.ddl +7 -0
  139. data/lib/mcollective/validator/bolt_task_name_validator.rb +11 -0
  140. data/lib/mcollective/validator/length_validator.rb +1 -3
  141. metadata +65 -6
  142. data/lib/mcollective/application/describe_filter.rb +0 -87
  143. data/lib/mcollective/matcher.rb +0 -220
  144. data/lib/mcollective/matcher/parser.rb +0 -128
  145. data/lib/mcollective/matcher/scanner.rb +0 -241
@@ -12,11 +12,11 @@ module MCollective
12
12
  load_application(appname)
13
13
  rescue Exception => e # rubocop:disable Lint/RescueException
14
14
  e.backtrace.first << Util.colorize(:red, " <----")
15
- STDERR.puts "Application '%s' failed to load:" % appname
16
- STDERR.puts
17
- STDERR.puts Util.colorize(:red, " %s (%s)" % [$!, $!.class])
18
- STDERR.puts
19
- STDERR.puts " %s" % [$!.backtrace.join("\n ")]
15
+ warn "Application '%s' failed to load:" % appname
16
+ $stderr.puts
17
+ warn Util.colorize(:red, " %s (%s)" % [$!, $!.class])
18
+ $stderr.puts
19
+ warn " %s" % [$!.backtrace.join("\n ")]
20
20
  exit 1
21
21
  end
22
22
 
@@ -40,7 +40,7 @@ module MCollective
40
40
  rescue SystemExit
41
41
  exit 1
42
42
  rescue Exception # rubocop:disable Lint/RescueException
43
- STDERR.puts("Failed to generate application list: %s: %s" % [$!.class, $!])
43
+ warn("Failed to generate application list: %s: %s" % [$!.class, $!])
44
44
  exit 1
45
45
  end
46
46
 
@@ -49,13 +49,14 @@ module MCollective
49
49
  def self.filter_extra_options(opts)
50
50
  words = Shellwords.shellwords(opts)
51
51
  words.each_with_index do |word, idx|
52
- if word == "-c"
52
+ case word
53
+ when "-c"
53
54
  return "--config=#{words[idx + 1]}"
54
- elsif word == "--config"
55
+ when "--config"
55
56
  return "--config=#{words[idx + 1]}"
56
- elsif word =~ /\-c=/
57
+ when /-c=/
57
58
  return word
58
- elsif word =~ /\-\-config=/
59
+ when /--config=/
59
60
  return word
60
61
  end
61
62
  end
@@ -0,0 +1,33 @@
1
+ module MCollective
2
+ module Audit
3
+ class Choria < RPC::Audit
4
+ def audit_request(request, connection)
5
+ logfile = Config.instance.pluginconf["rpcaudit.logfile"]
6
+
7
+ unless logfile
8
+ Log.warn("Auditing is not functional because rpcaudit.logfile is not set")
9
+ return
10
+ end
11
+
12
+ message = {
13
+ "timestamp" => Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%6N%z"),
14
+ "request_id" => request.uniqid,
15
+ "request_time" => request.time,
16
+ "caller" => request.caller,
17
+ "sender" => request.sender,
18
+ "agent" => request.agent,
19
+ "action" => request.action,
20
+ "data" => request.data
21
+ }
22
+
23
+ begin
24
+ File.open(logfile, "a") do |f|
25
+ f.puts(message.to_json)
26
+ end
27
+ rescue
28
+ Log.warn("Auditing is not functional because writing to logfile '%s' failed %s: %s" % [logfile, $!.class, $!.to_s])
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -122,14 +122,12 @@ module MCollective
122
122
  end
123
123
  end
124
124
 
125
- def self.synchronize(cache_name)
125
+ def self.synchronize(cache_name, &block)
126
126
  raise("No cache called '%s'" % cache_name) unless @cache.include?(cache_name)
127
127
 
128
128
  raise("No block supplied to synchronize") unless block_given?
129
129
 
130
- @cache_locks[cache_name].synchronize do
131
- yield
132
- end
130
+ @cache_locks[cache_name].synchronize(&block)
133
131
  end
134
132
 
135
133
  def self.invalidate!(cache_name, key)
@@ -7,10 +7,11 @@ module MCollective
7
7
  @config = Config.instance
8
8
  @options = nil
9
9
 
10
- if options.is_a?(String)
10
+ case options
11
+ when String
11
12
  # String is the path to a config file
12
13
  @config.loadconfig(options) unless @config.configured
13
- elsif options.is_a?(Hash)
14
+ when Hash
14
15
  @config.loadconfig(options[:config]) unless @config.configured
15
16
  @options = options
16
17
  @connection_timeout = options[:connection_timeout]
@@ -43,6 +44,10 @@ module MCollective
43
44
 
44
45
  @@request_sequence = 0 # rubocop:disable Style/ClassVars
45
46
 
47
+ def self.reset_request_sequence
48
+ @@request_sequence = 0 # rubocop:disable Style/ClassVars
49
+ end
50
+
46
51
  def self.request_sequence
47
52
  @@request_sequence
48
53
  end
@@ -124,9 +129,7 @@ module MCollective
124
129
 
125
130
  reply.decode!
126
131
 
127
- unless reply.requestid == requestid
128
- raise(MsgDoesNotMatchRequestID, "Message reqid #{reply.requestid} does not match our reqid #{requestid}")
129
- end
132
+ raise(MsgDoesNotMatchRequestID, "Message reqid #{reply.requestid} does not match our reqid #{requestid}") unless reply.requestid == requestid
130
133
 
131
134
  Log.debug("Received reply to #{reply.requestid} from #{reply.payload[:senderid]}")
132
135
  rescue SecurityValidationFailed => e
@@ -171,7 +174,7 @@ module MCollective
171
174
  request = createreq(body, agent, @options[:filter])
172
175
  publish_timeout = @options[:publish_timeout] || @config.publish_timeout
173
176
  stat = {:starttime => Time.now.to_f, :discoverytime => 0, :blocktime => 0, :totaltime => 0}
174
- STDOUT.sync = true
177
+ $stdout.sync = true
175
178
  hosts_responded = 0
176
179
 
177
180
  begin
@@ -180,7 +183,7 @@ module MCollective
180
183
  else
181
184
  hosts_responded = unthreaded_req(request, publish_timeout, timeout, waitfor, &block)
182
185
  end
183
- rescue Interrupt # rubocop:disable Lint/HandleExceptions
186
+ rescue Interrupt # rubocop:disable Lint/SuppressedException
184
187
  ensure
185
188
  unsubscribe(agent, :reply)
186
189
  end
@@ -277,9 +280,7 @@ module MCollective
277
280
  end
278
281
  rescue Timeout::Error
279
282
  if waitfor.is_a?(Array)
280
- unless unfinished.empty?
281
- Log.warn("Could not receive all responses. Did not receive responses from #{unfinished.keys.join(', ')}")
282
- end
283
+ Log.warn("Could not receive all responses. Did not receive responses from #{unfinished.keys.join(', ')}") unless unfinished.empty?
283
284
  elsif waitfor > hosts_responded
284
285
  Log.warn("Could not receive all responses. Expected : #{waitfor}. Received : #{hosts_responded}")
285
286
  end
@@ -5,19 +5,13 @@ module MCollective
5
5
 
6
6
  attr_accessor :mode
7
7
 
8
- attr_reader :daemonize, :pluginconf, :configured
9
- attr_reader :logfile, :keeplogs, :max_log_size, :loglevel, :logfacility
10
- attr_reader :identity, :connector, :securityprovider, :factsource
11
- attr_reader :registration, :registerinterval, :classesfile
12
- attr_reader :rpcauditprovider, :rpcaudit, :configdir, :rpcauthprovider
13
- attr_reader :rpcauthorization, :color, :configfile
14
- attr_reader :rpclimitmethod, :logger_type, :fact_cache_time, :collectives
15
- attr_reader :main_collective, :ssl_cipher, :registration_collective
16
- attr_reader :direct_addressing, :direct_addressing_threshold, :ttl
17
- attr_reader :default_discovery_method, :default_discovery_options
18
- attr_reader :publish_timeout, :threaded, :soft_shutdown, :activate_agents
19
- attr_reader :registration_splay, :discovery_timeout, :soft_shutdown_timeout
20
- attr_reader :connection_timeout, :default_batch_size, :default_batch_sleep_time
8
+ attr_reader :daemonize, :pluginconf, :configured, :logfile, :keeplogs, :max_log_size, :loglevel, :logfacility,
9
+ :identity, :connector, :securityprovider, :factsource, :registration, :registerinterval, :classesfile,
10
+ :rpcauditprovider, :rpcaudit, :configdir, :rpcauthprovider, :rpcauthorization, :color, :configfile,
11
+ :rpclimitmethod, :logger_type, :fact_cache_time, :collectives, :main_collective, :ssl_cipher, :registration_collective,
12
+ :direct_addressing, :direct_addressing_threshold, :ttl, :default_discovery_method, :default_discovery_options,
13
+ :publish_timeout, :threaded, :soft_shutdown, :activate_agents, :registration_splay, :discovery_timeout, :soft_shutdown_timeout,
14
+ :connection_timeout, :default_batch_size, :default_batch_sleep_time
21
15
 
22
16
  def initialize
23
17
  @configured = false
@@ -34,19 +28,12 @@ module MCollective
34
28
 
35
29
  next if line =~ /^#|^$/
36
30
  next unless line =~ /(.+?)\s*=\s*(.+)/
31
+
37
32
  key = $1.strip
38
33
  val = $2
39
34
 
40
35
  begin
41
36
  case key
42
- when "registration"
43
- @registration = val.capitalize
44
- when "registration_collective"
45
- @registration_collective = val
46
- when "registerinterval"
47
- @registerinterval = Integer(val)
48
- when "registration_splay"
49
- @registration_splay = Util.str_to_bool(val)
50
37
  when "collectives"
51
38
  @collectives = val.split(",").map(&:strip)
52
39
  when "main_collective"
@@ -128,10 +115,11 @@ module MCollective
128
115
  @default_batch_size = Integer(val)
129
116
  when "default_batch_sleep_time"
130
117
  @default_batch_sleep_time = Float(val)
131
- when "topicprefix", "topicsep", "queueprefix", "rpchelptemplate", "helptemplatedir"
132
- Log.warn("Use of deprecated '#{key}' option. This option is ignored and should be removed from '#{configfile}'")
133
118
  else
134
- raise("Unknown config parameter '#{key}'")
119
+ # server config might now be choria config which will divirge from mcollective
120
+ # in time, so we only raise this error when it looks like we aren't loading
121
+ # a server config else we try our best to load as much as we can
122
+ raise("Unknown config parameter '#{key}'") unless configfile =~ /server/
135
123
  end
136
124
  rescue ArgumentError
137
125
  raise("Could not parse value for configuration option '%s' with value '%s'" % [key, val])
@@ -140,7 +128,7 @@ module MCollective
140
128
 
141
129
  read_plugin_config_dir("#{@configdir}/plugin.d")
142
130
 
143
- raise 'Identities can only match /\w\.\-/' unless @identity =~ /^[\w\.\-]+$/
131
+ raise 'Identities can only match /\w\.\-/' unless @identity =~ /^[\w.\-]+$/
144
132
 
145
133
  @configured = true
146
134
 
@@ -152,14 +140,13 @@ module MCollective
152
140
  $LOAD_PATH.unshift dir
153
141
  end
154
142
 
155
- if @logger_type == "syslog"
156
- raise "The sylog logger is not usable on the Windows platform" if Util.windows?
157
- end
143
+ raise "The sylog logger is not usable on the Windows platform" if @logger_type == "syslog" && Util.windows?
158
144
 
159
- PluginManager.loadclass("Mcollective::Facts::#{@factsource}_facts")
160
- PluginManager.loadclass("Mcollective::Connector::#{@connector}")
161
- PluginManager.loadclass("Mcollective::Security::#{@securityprovider}")
162
- PluginManager << {:type => "global_stats", :class => RunnerStats.new}
145
+ unless configfile =~ /server/
146
+ PluginManager.loadclass("Mcollective::Facts::#{@factsource}_facts")
147
+ PluginManager.loadclass("Mcollective::Connector::#{@connector}")
148
+ PluginManager.loadclass("Mcollective::Security::#{@securityprovider}")
149
+ end
163
150
 
164
151
  Log.info("The Marionette Collective version #{MCollective::VERSION} started by #{$0} using config file #{configfile}")
165
152
  else
@@ -168,7 +155,6 @@ module MCollective
168
155
  end
169
156
 
170
157
  def set_config_defaults(configfile) # rubocop:disable Naming/AccessorMethodName
171
- @stomp = {}
172
158
  @subscribe = []
173
159
  @pluginconf = {}
174
160
  @connector = "base"
@@ -221,7 +207,7 @@ module MCollective
221
207
  return unless File.directory?(dir)
222
208
 
223
209
  Dir.new(dir).each do |pluginconfigfile|
224
- next unless pluginconfigfile =~ /^([\w]+).cfg$/
210
+ next unless pluginconfigfile =~ /^(\w+).cfg$/
225
211
 
226
212
  plugin = $1
227
213
  File.open("#{dir}/#{pluginconfigfile}", "r").each do |line|
@@ -229,6 +215,7 @@ module MCollective
229
215
  line.gsub!(/\s*$/, "")
230
216
  next if line =~ /^#|^$/
231
217
  next unless line =~ /(.+?)\s*=\s*(.+)/
218
+
232
219
  key = $1.strip
233
220
  val = $2
234
221
  @pluginconf["#{plugin}.#{key}"] = val
@@ -18,8 +18,9 @@ module MCollective
18
18
  class Base
19
19
  def self.inherited(klass)
20
20
  plugin_name = klass.to_s.split("::").last.downcase
21
- ddl = DDL.new(plugin_name, :connector)
21
+ DDL.new(plugin_name, :connector)
22
22
  PluginManager << {:type => "connector_plugin", :class => klass.to_s}
23
+ super
23
24
  end
24
25
  end
25
26
  end
@@ -0,0 +1,9 @@
1
+ metadata :name => "nats",
2
+ :description => "Connector plugin for NATS.io middleware",
3
+ :author => "R.I.Pienaar <rip@devco.net>",
4
+ :license => "Apache-2.0",
5
+ :version => "0.19.0",
6
+ :url => "https://github.com/choria-io",
7
+ :timeout => 60
8
+
9
+ requires :mcollective => "2.8.4"
@@ -0,0 +1,450 @@
1
+ require "resolv"
2
+ require_relative "../util/choria"
3
+ require_relative "../util/natswrapper"
4
+
5
+ module MCollective
6
+ module Connector
7
+ class Nats < Base
8
+ attr_reader :connection
9
+
10
+ def initialize # rubocop:disable Lint/MissingSuper
11
+ @config = Config.instance
12
+ @subscriptions = []
13
+ @connection = Util::NatsWrapper.new
14
+
15
+ Log.info("Choria NATS.io connector using pure ruby nats/io/client %s with protocol version %s" % [NATS::IO::VERSION, NATS::IO::PROTOCOL])
16
+ end
17
+
18
+ # Determines if the NATS connection is active
19
+ #
20
+ # @return [Boolean]
21
+ def connected?
22
+ connection.connected?
23
+ end
24
+
25
+ # Current connected server
26
+ #
27
+ # @return [String,nil]
28
+ def connected_server
29
+ connection.connected_server
30
+ end
31
+
32
+ # Retrieves the NATS connection stats
33
+ #
34
+ # @return [Hash]
35
+ def stats
36
+ connection.stats
37
+ end
38
+
39
+ # Client library version
40
+ #
41
+ # @return [String]
42
+ def client_version
43
+ connection.client_version
44
+ end
45
+
46
+ # Client library flavour
47
+ #
48
+ # @return [String]
49
+ def client_flavour
50
+ connection.client_flavour
51
+ end
52
+
53
+ # Connection options from the NATS gem
54
+ #
55
+ # @return [Hash]
56
+ def active_options
57
+ connection.active_options
58
+ end
59
+
60
+ # Attempts to connect to the middleware, noop when already connected
61
+ #
62
+ # @return [void]
63
+ # @raise [StandardError] when SSL files are not readable
64
+ def connect
65
+ if connection && connection.started?
66
+ Log.debug("Already connection, not re-initializing connection")
67
+ return
68
+ end
69
+
70
+ parameters = {
71
+ :max_reconnect_attempts => -1,
72
+ :reconnect_time_wait => 1,
73
+ :dont_randomize_servers => !choria.randomize_middleware_servers?,
74
+ :name => @config.identity
75
+ }
76
+
77
+ parameters[:user_credentials] = choria.credential_file if choria.credential_file?
78
+
79
+ if $choria_unsafe_disable_nats_tls # rubocop:disable Style/GlobalVars
80
+ Log.warn("Disabling TLS in NATS connector, this is not a production supported setup")
81
+ elsif choria.ngs?
82
+ configure_ngs(parameters)
83
+ else
84
+ configure_tls(parameters)
85
+ end
86
+
87
+ servers = server_list
88
+
89
+ unless servers.empty?
90
+ Log.debug("Connecting to servers: %s" % servers.join(", "))
91
+ parameters[:servers] = servers
92
+ end
93
+
94
+ connection.start(parameters)
95
+
96
+ nil
97
+ end
98
+
99
+ def configure_tls(parameters)
100
+ parameters[:tls] = {:context => choria.ssl_context}
101
+ choria.check_ssl_setup
102
+ end
103
+
104
+ def configure_ngs(parameters)
105
+ Log.debug("Disabling specific TLS during connection to NGS")
106
+
107
+ raise("nkeys rubygem is required for connections with credentials") unless choria.nkeys?
108
+
109
+ tls = OpenSSL::SSL::SSLContext.new
110
+ tls.ssl_version = :TLSv1_2 # rubocop:disable Naming/VariableNumber
111
+
112
+ parameters[:tls] = {:context => tls}
113
+ end
114
+
115
+ # Disconnects from NATS
116
+ def disconnect
117
+ connection.stop
118
+ end
119
+
120
+ # Creates the middleware headers needed for a given message
121
+ #
122
+ # @param msg [Message]
123
+ # @return [Hash]
124
+ def headers_for(msg)
125
+ # mc_sender is only passed bacause M::Message incorrectly assumed this is some required
126
+ # part of messages when its just some internals of the stomp based connectors that bled out
127
+ headers = {
128
+ "mc_sender" => @config.identity
129
+ }
130
+
131
+ headers["seen-by"] = [] if msg.headers.include?("seen-by")
132
+
133
+ if [:request, :direct_request].include?(msg.type)
134
+ if msg.reply_to
135
+ headers["reply-to"] = msg.reply_to
136
+ else
137
+ # if its a request/direct_request style message and its not
138
+ # one we're replying to - ie. its a new message we're making
139
+ # we'll need to set a reply-to target that the daemon will
140
+ # subscribe to
141
+ headers["reply-to"] = make_target(msg.agent, :reply, msg.collective)
142
+ end
143
+
144
+ headers["seen-by"] << [@config.identity, connected_server.to_s] if msg.headers.include?("seen-by")
145
+ elsif msg.type == :reply
146
+ if msg.request.headers.include?("seen-by")
147
+ headers["seen-by"] = msg.request.headers["seen-by"]
148
+ headers["seen-by"].last << connected_server.to_s
149
+ end
150
+ end
151
+
152
+ headers
153
+ end
154
+
155
+ # Create a target structure for a message
156
+ #
157
+ # @example data
158
+ #
159
+ # {
160
+ # :name => "nats.name",
161
+ # :headers => { headers... }
162
+ # }
163
+ #
164
+ # @param msg [Message]
165
+ # @param identity [String,nil] override identity
166
+ # @return [Hash]
167
+ def target_for(msg, identity=nil)
168
+ target = nil
169
+
170
+ if msg.type == :reply
171
+ raise("Do not know how to reply, no reply-to header has been set on message %s" % msg.requestid) unless msg.request.headers["reply-to"]
172
+
173
+ target = {:name => msg.request.headers["reply-to"], :headers => {}}
174
+
175
+ elsif [:request, :direct_request].include?(msg.type)
176
+ target = {:name => make_target(msg.agent, msg.type, msg.collective, identity), :headers => {}}
177
+
178
+ else
179
+ raise("Don't now how to create a target for message type %s" % msg.type)
180
+
181
+ end
182
+
183
+ target[:headers].merge!(headers_for(msg))
184
+
185
+ target
186
+ end
187
+
188
+ # Retrieves the current process pid
189
+ #
190
+ # @note mainly used for testing
191
+ # @return [Fixnum]
192
+ def current_pid
193
+ $$
194
+ end
195
+
196
+ # Creates a target structure
197
+ #
198
+ # @param agent [String] agent name
199
+ # @param type [:directed, :broadcast, :reply, :request, :direct_request]
200
+ # @param collective [String] target collective name
201
+ # @param identity [String,nil] identity for the request, else node configured identity
202
+ # @return [String] target name
203
+ # @raise [StandardError] on invalid input
204
+ def make_target(agent, type, collective, identity=nil)
205
+ raise("Unknown target type %s" % type) unless [:directed, :broadcast, :reply, :request, :direct_request].include?(type)
206
+
207
+ raise("Unknown collective '%s' known collectives are '%s'" % [collective, @config.collectives.join(", ")]) unless @config.collectives.include?(collective)
208
+
209
+ identity ||= @config.identity
210
+
211
+ case type
212
+ when :reply
213
+ "%s.reply.%s.%d.%d" % [collective, Digest::MD5.hexdigest(choria.callerid), current_pid, Client.request_sequence]
214
+
215
+ when :broadcast, :request
216
+ "%s.broadcast.agent.%s" % [collective, agent]
217
+
218
+ when :direct_request, :directed
219
+ "%s.node.%s" % [collective, identity]
220
+ end
221
+ end
222
+
223
+ # Publishes a message to the middleware
224
+ #
225
+ # @param msg [Message]
226
+ def publish(msg)
227
+ msg.base64_encode!
228
+
229
+ if choria.federated?
230
+ msg.type == :direct_request ? publish_federated_directed(msg) : publish_federated_broadcast(msg)
231
+ else
232
+ msg.type == :direct_request ? publish_connected_directed(msg) : publish_connected_broadcast(msg)
233
+ end
234
+ end
235
+
236
+ # Publish a directed request via a Federation Broker
237
+ #
238
+ # @param msg [Message]
239
+ def publish_federated_directed(msg)
240
+ messages = []
241
+ target = target_for(msg, msg.discovered_hosts[0])
242
+
243
+ msg.discovered_hosts.in_groups_of(200) do |nodes|
244
+ node_targets = nodes.compact.map do |node|
245
+ target_for(msg, node)[:name]
246
+ end
247
+
248
+ data = {
249
+ "protocol" => "choria:transport:1",
250
+ "data" => msg.payload,
251
+ "headers" => {
252
+ "federation" => {
253
+ "target" => node_targets,
254
+ "req" => msg.requestid
255
+ }
256
+ }.merge(target[:headers])
257
+ }
258
+
259
+ messages << JSON.dump(data)
260
+ end
261
+
262
+ choria.federation_collectives.each do |network|
263
+ messages.each do |data|
264
+ network_target = "choria.federation.%s.federation" % network
265
+
266
+ Log.debug("Sending a federated direct message via NATS target '%s' for message type %s" % [network_target, msg.type])
267
+
268
+ connection.publish(network_target, data, target[:headers]["reply-to"])
269
+ end
270
+ end
271
+ end
272
+
273
+ # Publish a directed request to a connected collective
274
+ #
275
+ # @param msg [Message]
276
+ def publish_connected_directed(msg)
277
+ msg.discovered_hosts.each do |node|
278
+ target = target_for(msg, node)
279
+ data = {
280
+ "protocol" => "choria:transport:1",
281
+ "data" => msg.payload,
282
+ "headers" => target[:headers]
283
+ }
284
+
285
+ Log.debug("Sending a direct message to %s via NATS target '%s' for message type %s" % [node, target.inspect, msg.type])
286
+
287
+ connection.publish(target[:name], data.to_json, target[:headers]["reply-to"])
288
+ end
289
+ end
290
+
291
+ # Publish a broadcast message to via a Federation Broker
292
+ #
293
+ # @param msg [Message]
294
+ def publish_federated_broadcast(msg)
295
+ target = target_for(msg)
296
+ data = {
297
+ "protocol" => "choria:transport:1",
298
+ "data" => msg.payload,
299
+ "headers" => {
300
+ "federation" => {
301
+ "target" => [target[:name]],
302
+ "req" => msg.requestid
303
+ }
304
+ }.merge(target[:headers])
305
+ }
306
+
307
+ data = JSON.dump(data)
308
+
309
+ choria.federation_collectives.each do |network|
310
+ target[:name] = "choria.federation.%s.federation" % network
311
+
312
+ Log.debug("Sending a federated broadcast message to NATS target '%s' for message type %s" % [target.inspect, msg.type])
313
+
314
+ connection.publish(target[:name], data, target[:headers]["reply-to"])
315
+ end
316
+ end
317
+
318
+ # Publish a broadcast message to a connected collective
319
+ #
320
+ # @param msg [Message]
321
+ def publish_connected_broadcast(msg)
322
+ target = target_for(msg)
323
+ data = {
324
+ "protocol" => "choria:transport:1",
325
+ "data" => msg.payload,
326
+ "headers" => target[:headers]
327
+ }
328
+
329
+ # only happens when replying
330
+ received_message = msg.request
331
+ data["headers"]["federation"] = received_message.headers["federation"] if received_message && received_message.headers.include?("federation")
332
+
333
+ Log.debug("Sending a broadcast message to NATS target '%s' for message type %s" % [target.inspect, msg.type])
334
+
335
+ connection.publish(target[:name], JSON.dump(data), target[:headers]["reply-to"])
336
+ end
337
+
338
+ # Unsubscribe from the target for a agent
339
+ #
340
+ # @see make_target
341
+ # @param agent [String] agent name
342
+ # @param type [:reply, :broadcast, :request, :direct_request, :directed] type of message you want a subscription for
343
+ # @param collective [String] the collective to subscribe for
344
+ # @return [void]
345
+ def unsubscribe(agent, type, collective)
346
+ target = make_target(agent, type, collective)
347
+ Log.debug("Unsubscribing from %s" % target)
348
+
349
+ connection.unsubscribe(target)
350
+ end
351
+
352
+ # Subscribes to the topics/queues needed for a particular agent
353
+ #
354
+ # @see make_target
355
+ # @param agent [String] agent name
356
+ # @param type [:reply, :broadcast, :request, :direct_request, :directed] type of message you want a subscription for
357
+ # @param collective [String] the collective to subscribe for
358
+ # @return [void]
359
+ def subscribe(agent, type, collective)
360
+ target = make_target(agent, type, collective)
361
+
362
+ connection.subscribe(target)
363
+ end
364
+
365
+ # Receives a message from the middleware
366
+ #
367
+ # @note blocks until one is received
368
+ # @return [Message]
369
+ def receive
370
+ msg = nil
371
+
372
+ until msg
373
+ received = connection.receive
374
+
375
+ Thread.pass
376
+
377
+ begin
378
+ msg = JSON.parse(received)
379
+ rescue
380
+ Log.warn("Got non JSON data from the broker: %s" % [received])
381
+ msg = nil
382
+ end
383
+ end
384
+
385
+ msg["headers"]["seen-by"] << [connected_server.to_s, @config.identity] if msg["headers"].include?("seen-by")
386
+
387
+ Message.new(msg["data"], msg, :base64 => true, :headers => msg["headers"])
388
+ end
389
+
390
+ # Retrieves the list of server and port combos to attempt to connect to
391
+ #
392
+ # Configured servers are checked, then SRV records and finally a fall
393
+ # back to puppet:4222 is done
394
+ #
395
+ # @return [Array<String>] list of servers in form of a URI
396
+ def server_list
397
+ uris = choria.middleware_servers("puppet", "4222").map do |host, port|
398
+ URI("nats://%s:%s" % [host, port])
399
+ end
400
+
401
+ decorate_servers_with_users(uris).map(&:to_s)
402
+ end
403
+
404
+ # Add user and pass to a series of URIs
405
+ #
406
+ # @param servers [Array<URI>] list of URI's to decorate
407
+ # @return [Array<URI>]
408
+ def decorate_servers_with_users(servers)
409
+ user = get_option("nats.user", environment["MCOLLECTIVE_NATS_USERNAME"])
410
+ pass = get_option("nats.pass", environment["MCOLLECTIVE_NATS_PASSWORD"])
411
+
412
+ if choria.anon_tls?
413
+ user = PluginManager["security_plugin"].request_signer.token
414
+ pass = nil
415
+ end
416
+
417
+ if user || pass
418
+ servers.each do |uri|
419
+ uri.user = user
420
+ uri.password = pass
421
+ end
422
+ end
423
+
424
+ servers
425
+ end
426
+
427
+ # Retrieves the environment, mainly used for testing
428
+ def environment
429
+ ENV
430
+ end
431
+
432
+ # Gets a config option
433
+ #
434
+ # @param opt [String] config option to look up
435
+ # @param default [Object] default to return when not found
436
+ # @return [Object] the found data or default
437
+ # @raise [StandardError] when no default is given and option is not found
438
+ def get_option(opt, default=:_unset)
439
+ return @config.pluginconf[opt] if @config.pluginconf.include?(opt)
440
+ return default unless default == :_unset
441
+
442
+ raise("No plugin.%s configuration option given" % opt)
443
+ end
444
+
445
+ def choria
446
+ @_choria ||= Util::Choria.new(false)
447
+ end
448
+ end
449
+ end
450
+ end