choria-mcorpc-support 2.21.1 → 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.
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 +58 -55
  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
@@ -0,0 +1,93 @@
1
+ module MCollective
2
+ module Util
3
+ class Playbook
4
+ class Tasks
5
+ class ShellTask < Base
6
+ def validate_configuration!
7
+ raise("A command was not given") unless @command
8
+ raise("Nodes were given but is not an array") if @nodes && !@nodes.is_a?(Array)
9
+ end
10
+
11
+ def from_hash(data)
12
+ @cwd = data["cwd"] || Dir.pwd
13
+ @timeout = data["timeout"]
14
+ @nodes = data["nodes"]
15
+ @environment = data["environment"]
16
+
17
+ if @nodes.is_a?(Array)
18
+ @command = "%s --nodes %s" % [data["command"], @nodes.join(",")]
19
+ else
20
+ @command = data["command"]
21
+ end
22
+
23
+ self
24
+ end
25
+
26
+ def shell_options
27
+ options = {}
28
+ options["cwd"] = @cwd if @cwd
29
+ options["timeout"] = Integer(@timeout) if @timeout
30
+ options["environment"] = @environment if @environment
31
+ options["stdout"] = []
32
+ options["stderr"] = options["stdout"]
33
+ options
34
+ end
35
+
36
+ def to_execution_result(results)
37
+ result = {
38
+ "value" => results[2].join("\r\n").chomp,
39
+ "type" => "shell",
40
+ "fail_ok" => @fail_ok,
41
+ "error" => {
42
+ "msg" => results[1],
43
+ "kind" => "choria.playbook/taskerror",
44
+ "details" => {
45
+ "command" => @command
46
+ }
47
+ }
48
+ }
49
+
50
+ result.delete("error") if results[0]
51
+
52
+ {"localhost" => result}
53
+ end
54
+
55
+ def run
56
+ if @nodes
57
+ Log.info("Starting command %s against %d nodes" % [@command, @nodes.size])
58
+ else
59
+ Log.info("Starting command %s" % [@command])
60
+ end
61
+
62
+ begin
63
+ options = shell_options
64
+
65
+ shell = Shell.new(@command, options)
66
+ shell.runcommand
67
+
68
+ options["stdout"].each do |output|
69
+ output.lines.each do |line|
70
+ Log.info(line.chomp)
71
+ end
72
+ end
73
+
74
+ if shell.status.exitstatus == 0
75
+ Log.info("Successfully ran command %s" % [@command])
76
+ [true, "Command completed successfully", options["stdout"]]
77
+ else
78
+ Log.warn("Failed to run command %s with exit code %s" % [@command, shell.status.exitstatus])
79
+ [false, "Command failed with code %d" % [shell.status.exitstatus], options["stdout"]]
80
+ end
81
+ rescue
82
+ msg = "Could not run command %s: %s: %s" % [@command, $!.class, $!.to_s]
83
+ Log.debug(msg)
84
+ Log.debug($!.backtrace.join("\t\n"))
85
+
86
+ [false, msg, []]
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,105 @@
1
+ require "cgi"
2
+ require "json"
3
+
4
+ module MCollective
5
+ module Util
6
+ class Playbook
7
+ class Tasks
8
+ class SlackTask < Base
9
+ def validate_configuration!
10
+ raise("A channel is required") unless @channel
11
+ raise("Message text is required") unless @text
12
+ raise("A bot token is required") unless @token
13
+ end
14
+
15
+ def from_hash(data)
16
+ @channel = data["channel"]
17
+ @text = data["text"]
18
+ @token = data["token"]
19
+ @color = data.fetch("color", "#ffa449")
20
+ @username = data.fetch("username", "Choria")
21
+ @icon = "https://choria.io/img/slack-48x48.png"
22
+
23
+ self
24
+ end
25
+
26
+ def choria
27
+ @_choria ||= Util::Choria.new(false)
28
+ end
29
+
30
+ def attachments
31
+ [
32
+ "fallback" => @text,
33
+ "color" => @color,
34
+ "text" => @text,
35
+ "pretext" => "Task: %s" % @description,
36
+ "mrkdwn_in" => ["text"],
37
+ "footer" => "Choria Playbooks",
38
+ "fields" => [
39
+ {
40
+ "title" => "user",
41
+ "value" => PluginManager["security_plugin"].callerid,
42
+ "short" => true
43
+ },
44
+ {
45
+ "title" => "playbook",
46
+ "value" => @playbook.name,
47
+ "short" => true
48
+ }
49
+ ]
50
+ ]
51
+ end
52
+
53
+ def to_execution_result(results)
54
+ result = {
55
+ "value" => results[1],
56
+ "type" => "slack",
57
+ "fail_ok" => @fail_ok
58
+ }
59
+
60
+ unless results[0]
61
+ result["error"] = {
62
+ "msg" => results[1],
63
+ "kind" => "choria.playbook/taskerror",
64
+ "details" => {
65
+ "channel" => @channel
66
+ }
67
+ }
68
+ result["value"] = results[2].first
69
+ end
70
+
71
+ {"slack.com" => result}
72
+ end
73
+
74
+ def run
75
+ https = choria.https(:target => "slack.com", :port => 443)
76
+ path = "/api/chat.postMessage?token=%s&username=%s&channel=%s&icon_url=%s&attachments=%s" % [
77
+ CGI.escape(@token),
78
+ CGI.escape(@username),
79
+ CGI.escape(@channel),
80
+ CGI.escape(@icon),
81
+ CGI.escape(attachments.to_json)
82
+ ]
83
+
84
+ resp, data = https.request(choria.http_get(path))
85
+ data = JSON.parse(data || resp.body)
86
+
87
+ if resp.code == "200" && data["ok"]
88
+ Log.debug("Successfully sent message to slack channel %s" % [@channel])
89
+ [true, "Message submitted to slack channel %s" % [@channel], [data]]
90
+ else
91
+ Log.warn("Failed to send message to slack channel %s: %s" % [@channel, data["error"]])
92
+ [false, "Failed to send message to slack channel %s: %s" % [@channel, data["error"]], [data]]
93
+ end
94
+ rescue
95
+ msg = "Could not publish slack message to channel %s: %s: %s" % [@channel, $!.class, $!.to_s]
96
+ Log.debug(msg)
97
+ Log.debug($!.backtrace.join("\t\n"))
98
+
99
+ [false, msg, []]
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,136 @@
1
+ require "uri"
2
+ require "cgi"
3
+ require "json"
4
+
5
+ module MCollective
6
+ module Util
7
+ class Playbook
8
+ class Tasks
9
+ class WebhookTask < Base
10
+ USER_AGENT = "Choria Playbooks http://choria.io".freeze
11
+
12
+ attr_reader :verify_ssl
13
+
14
+ def validate_configuration!
15
+ raise("A uri is required") unless @uri
16
+ raise("Only GET and POST is supported as methods") unless ["GET", "POST"].include?(@method)
17
+ end
18
+
19
+ def from_hash(data)
20
+ @headers = data.fetch("headers", {})
21
+ @data = data.fetch("data", {})
22
+ @uri = data["uri"]
23
+ @method = data.fetch("method", "POST").upcase
24
+ @request_id = SSL.uuid
25
+ @verify_ssl = Util.str_to_bool(data.fetch("verify_ssl", true))
26
+
27
+ self
28
+ end
29
+
30
+ def create_uri
31
+ uri = URI.parse(@uri)
32
+
33
+ if @method == "GET"
34
+ query = Array(uri.query)
35
+
36
+ @data.each do |k, v|
37
+ query << "%s=%s" % [CGI.escape(k), CGI.escape(v.to_s)]
38
+ end
39
+
40
+ uri.query = query.join("&") unless query.empty?
41
+ end
42
+
43
+ uri
44
+ end
45
+
46
+ def http_get_request(uri)
47
+ headers = {
48
+ "User-Agent" => USER_AGENT,
49
+ "X-Choria-Request-ID" => @request_id
50
+ }.merge(@headers)
51
+
52
+ Net::HTTP::Get.new(uri.request_uri, headers)
53
+ end
54
+
55
+ def http_post_request(uri)
56
+ headers = {
57
+ "Content-Type" => "application/json",
58
+ "User-Agent" => USER_AGENT,
59
+ "X-Choria-Request-ID" => @request_id
60
+ }.merge(@headers)
61
+
62
+ req = Net::HTTP::Post.new(uri.request_uri, headers)
63
+ req.body = @data.to_json
64
+ req
65
+ end
66
+
67
+ def http_request(uri)
68
+ return http_get_request(uri) if @method == "GET"
69
+ return http_post_request(uri) if @method == "POST"
70
+
71
+ raise("Unknown request method %s" % @method)
72
+ end
73
+
74
+ def choria
75
+ @_choria ||= Util::Choria.new(false)
76
+ end
77
+
78
+ def http(uri)
79
+ http = choria.https({:target => uri.host, :port => uri.port}, false)
80
+
81
+ if verify_ssl
82
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
83
+ else
84
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
85
+ end
86
+
87
+ http.use_ssl = false if uri.scheme == "http"
88
+
89
+ http
90
+ end
91
+
92
+ def to_execution_result(results)
93
+ result = {
94
+ "value" => results[2].first,
95
+ "type" => "webhook",
96
+ "fail_ok" => @fail_ok,
97
+ "error" => {
98
+ "msg" => results[1],
99
+ "kind" => "choria.playbook/taskerror",
100
+ "details" => {
101
+ "task" => "webhook",
102
+ "uri" => @uri,
103
+ "method" => @method,
104
+ "request_id" => @request_id
105
+ }
106
+ }
107
+ }
108
+
109
+ result.delete("error") if results[0]
110
+
111
+ {create_uri.host => result}
112
+ end
113
+
114
+ def run
115
+ uri = create_uri
116
+ resp = http(uri).request(http_request(uri))
117
+
118
+ Log.debug("%s request to %s returned code %s with body: %s" % [@method, uri.to_s, resp.code, resp.body])
119
+
120
+ if ["200", "201"].include?(resp.code)
121
+ [true, "Successfully sent %s request to webhook %s with id %s" % [@method, @uri, @request_id], [resp.body]]
122
+ else
123
+ [false, "Failed to send %s request to webhook %s with id %s: %s: %s" % [@method, @uri, @request_id, resp.code, resp.body], [resp.body]]
124
+ end
125
+ rescue
126
+ msg = "Could not send %s to webhook %s: %s: %s" % [@method, @uri, $!.class, $!.to_s]
127
+ Log.debug(msg)
128
+ Log.debug($!.backtrace.join("\t\n"))
129
+
130
+ [false, msg, []]
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,98 @@
1
+ module MCollective
2
+ module Util
3
+ class Playbook
4
+ module TemplateUtil
5
+ # Recursively parse a data structure seeking strings that might contain templates
6
+ #
7
+ # Template strings look like `{{{scope.key}}}` where scope is one of `input`, `metadata`,
8
+ # `nodes` and the key is some item contained in those scopes like a named node list.
9
+ #
10
+ # You'll generally mix this into a class you wish to use it in, that class should have
11
+ # a `@playbook` variable set which is an instance of `Playbook`
12
+ #
13
+ # @param data [Object] data structure to traverse
14
+ # @return [Object] deep cloned copy of the structure with strings parsed
15
+ def t(data)
16
+ data = Marshal.load(Marshal.dump(data))
17
+
18
+ case data
19
+ when String
20
+ __template_process_string(data)
21
+ when Hash
22
+ data.each do |k, v|
23
+ data[k] = t(v)
24
+ end
25
+
26
+ data
27
+ when Array
28
+ data.map do |v|
29
+ t(v)
30
+ end
31
+ else
32
+ data
33
+ end
34
+ end
35
+
36
+ def __template_resolve(type, item)
37
+ Log.debug("Resolving template data for %s.%s" % [type, item])
38
+
39
+ case type
40
+ when "input", "inputs"
41
+ @playbook.input_value(item)
42
+ when "nodes"
43
+ @playbook.discovered_nodes(item)
44
+ when "metadata"
45
+ @playbook.metadata_item(item)
46
+ when "previous_task"
47
+ @playbook.previous_task(item)
48
+ when "date"
49
+ Time.now.strftime(item)
50
+ when "utc_date"
51
+ Time.now.utc.strftime(item)
52
+ when "elapsed_time"
53
+ @playbook.report.elapsed_time
54
+ when "uuid"
55
+ SSL.uuid
56
+ else
57
+ raise("Do not know how to process data of type %s" % type)
58
+ end
59
+ end
60
+
61
+ def __template_process_string(string)
62
+ raise("Playbook is not accessible") unless @playbook
63
+
64
+ front = '{{2,3}\s*'
65
+ back = '\s*}{2,3}'
66
+
67
+ data_regex = Regexp.new("%s%s%s" % [front, '(?<type>input(s*)|metadata|nodes)\.(?<item>[a-zA-Z0-9\_\-]+)', back])
68
+ date_regex = Regexp.new("%s%s%s" % [front, '(?<type>date|utc_date)\(\s*["\']*(?<format>.+?)["\']*\s*\)', back])
69
+ task_regex = Regexp.new("%s%s%s" % [front, '(?<type>previous_task)\.(?<item>(success|description|msg|message|data|runtime))', back])
70
+ singles_regex = Regexp.new("%s%s%s" % [front, "(?<type>uuid|elapsed_time)", back])
71
+
72
+ combined_regex = Regexp.union(data_regex, date_regex, task_regex, singles_regex)
73
+
74
+ if req = (string.match(/^#{data_regex}$/) || string.match(/^#{task_regex}$/))
75
+ __template_resolve(req["type"], req["item"])
76
+ elsif req = string.match(/^#{date_regex}$/)
77
+ __template_resolve(req["type"], req["format"])
78
+ elsif req = string.match(/^#{singles_regex}$/)
79
+ __template_resolve(req["type"], "")
80
+ else
81
+ string.gsub(/#{combined_regex}/) do |part|
82
+ value = __template_process_string(part)
83
+
84
+ case value
85
+ when Array
86
+ value.join(", ")
87
+ when Hash
88
+ value.to_json
89
+ else
90
+ value
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,169 @@
1
+ module MCollective
2
+ module Util
3
+ class Playbook
4
+ class Uses
5
+ def initialize(playbook)
6
+ @playbook = playbook
7
+ @uses = {}
8
+ end
9
+
10
+ def [](agent)
11
+ @uses[agent]
12
+ end
13
+
14
+ def keys
15
+ @uses.keys
16
+ end
17
+
18
+ # Retrieves the agent inventory for a set of nodes
19
+ #
20
+ # @param nodes [Array<String>] list of nodes to retrieve it for
21
+ # @return [Boolean, String, Array] success, message and inventory results
22
+ def agent_inventory(nodes)
23
+ rpc = Tasks::McollectiveTask.new(@playbook)
24
+
25
+ rpc.from_hash(
26
+ "nodes" => nodes,
27
+ "action" => "rpcutil.agent_inventory",
28
+ "silent" => true
29
+ )
30
+
31
+ success, msg, inventory = rpc.run
32
+
33
+ # rpcutil#agent_inventory is a hash in a hash not managed by the DDL
34
+ # this is not handled by the JSON encoding magic that does DDL based
35
+ # symbol and string conversion so we normalise the data always to symbol
36
+ # based structures
37
+ inventory.each do |node|
38
+ node["data"][:agents].each do |agent|
39
+ agent.keys.each do |key| # rubocop:disable Style/HashEachMethods
40
+ agent[key.intern] = agent.delete(key) if key.is_a?(String)
41
+ end
42
+ end
43
+ end
44
+
45
+ [success, msg, inventory]
46
+ end
47
+
48
+ # Validates agent versions on nodes
49
+ #
50
+ # @param agents [Hash] a hash of agent names and nodes that uses that agent
51
+ # @raise [StandardError] on failure
52
+ def validate_agents(agents)
53
+ nodes = agents.map {|_, agent_nodes| agent_nodes}.flatten.uniq
54
+
55
+ Log.info("Validating agent inventory on %d nodes" % nodes.size)
56
+
57
+ validation_fail = false
58
+
59
+ success, msg, inventory = agent_inventory(nodes)
60
+
61
+ raise("Could not determine agent inventory: %s" % msg) unless success
62
+
63
+ agents.each do |agent, agent_nodes|
64
+ unless @uses.include?(agent)
65
+ Log.warn("Agent %s is mentioned in node sets but not declared in the uses list" % agent)
66
+ validation_fail = true
67
+ next
68
+ end
69
+
70
+ agent_nodes.each do |node|
71
+ unless node_inventory = inventory.find {|i| i["sender"] == node}
72
+ Log.warn("Did not receive an inventory for node %s" % node)
73
+ validation_fail = true
74
+ next
75
+ end
76
+
77
+ unless metadata = node_inventory["data"][:agents].find {|i| i[:agent] == agent}
78
+ Log.warn("Node %s does not have the agent %s" % [node, agent])
79
+ validation_fail = true
80
+ next
81
+ end
82
+
83
+ if valid_version?(metadata[:version], @uses[agent])
84
+ Log.debug("Agent %s on %s version %s matches desired version %s" % [agent, node, metadata[:version], @uses[agent]])
85
+ else
86
+ Log.warn("Agent %s on %s version %s does not match desired version %s" % [agent, node, metadata[:version], @uses[agent]])
87
+ validation_fail = true
88
+ end
89
+ end
90
+ end
91
+
92
+ raise("Network agents did not match specified SemVer specifications in the playbook") if validation_fail
93
+
94
+ Log.info("Agent inventory on %d nodes validated" % nodes.size)
95
+ end
96
+
97
+ # Determines if a semver version is within a stated range
98
+ #
99
+ # @note mcollective never suggested semver, so versions like "1.1" becomes "1.1.0" for the compare
100
+ # @param have [String] SemVer of what you have
101
+ # @param want [String] SemVer range of what you need
102
+ # @return [Boolean]
103
+ # @raise [StandardError] on invalid version strings
104
+ def valid_version?(have, want)
105
+ have = "%s.0" % have if have.split(".").size == 2
106
+
107
+ require "semantic_puppet" unless defined?(SemanticPuppet)
108
+
109
+ semver_have = SemanticPuppet::Version.parse(have)
110
+ semver_want = SemanticPuppet::VersionRange.parse(want)
111
+ semver_want.include?(semver_have)
112
+ end
113
+
114
+ # Checks that all the declared agent DDLs exist
115
+ #
116
+ # @raise [StandardError] on invalid DDLs
117
+ def prepare
118
+ invalid = @uses.map do |agent, want|
119
+ begin
120
+ have = ddl_version(agent)
121
+
122
+ if valid_version?(have, want)
123
+ Log.debug("Agent %s DDL version %s matches desired %s" % [agent, have, want])
124
+ nil
125
+ else
126
+ Log.warn("Agent %s DDL version %s does not match desired %s" % [agent, have, want])
127
+ agent
128
+ end
129
+ rescue
130
+ Log.warn("Could not process DDL for agent %s: %s: %s" % [agent, $!.class, $!.to_s])
131
+ agent
132
+ end
133
+ end.compact
134
+
135
+ raise("DDLs for agent(s) %s did not match desired versions" % invalid.join(", ")) unless invalid.empty?
136
+ end
137
+
138
+ # Fetches the DDL version for an agent
139
+ #
140
+ # If the agent DDL has versions like 1.0 it will be
141
+ # turned into 1.0.0 as old mco stuff didnt do semver
142
+ #
143
+ # @param agent [String]
144
+ def ddl_version(agent)
145
+ ddl = agent_ddl(agent)
146
+ ddl.meta[:version]
147
+ end
148
+
149
+ # Returns the DDL for a specific agent
150
+ #
151
+ # @param agent [String]
152
+ # @return [DDL::AgentDDL]
153
+ # @raise [StandardError] should the DDL not exist
154
+ def agent_ddl(agent)
155
+ DDL::AgentDDL.new(agent)
156
+ end
157
+
158
+ def from_hash(data)
159
+ data.each do |agent, version|
160
+ Log.debug("Loading usage of %s version %s" % [agent, version])
161
+ @uses[agent] = version
162
+ end
163
+
164
+ self
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end