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.
- checksums.yaml +4 -4
- data/lib/mcollective.rb +1 -2
- data/lib/mcollective/agent/bolt_tasks.ddl +253 -0
- data/lib/mcollective/agent/bolt_tasks.json +365 -0
- data/lib/mcollective/agent/bolt_tasks.rb +178 -0
- data/lib/mcollective/agent/choria_util.ddl +152 -0
- data/lib/mcollective/agent/choria_util.json +244 -0
- data/lib/mcollective/agent/rpcutil.ddl +8 -4
- data/lib/mcollective/agent/rpcutil.json +333 -0
- data/lib/mcollective/agent/scout.ddl +169 -0
- data/lib/mcollective/agent/scout.json +224 -0
- data/lib/mcollective/agents.rb +7 -6
- data/lib/mcollective/aggregate.rb +4 -4
- data/lib/mcollective/aggregate/average.rb +2 -2
- data/lib/mcollective/aggregate/base.rb +2 -2
- data/lib/mcollective/aggregate/result.rb +3 -3
- data/lib/mcollective/aggregate/result/collection_result.rb +2 -2
- data/lib/mcollective/aggregate/result/numeric_result.rb +2 -2
- data/lib/mcollective/aggregate/sum.rb +2 -2
- data/lib/mcollective/aggregate/summary.rb +3 -4
- data/lib/mcollective/application.rb +57 -21
- data/lib/mcollective/application/choria.rb +189 -0
- data/lib/mcollective/application/completion.rb +6 -6
- data/lib/mcollective/application/facts.rb +11 -11
- data/lib/mcollective/application/federation.rb +237 -0
- data/lib/mcollective/application/find.rb +4 -4
- data/lib/mcollective/application/help.rb +3 -3
- data/lib/mcollective/application/inventory.rb +3 -341
- data/lib/mcollective/application/ping.rb +5 -51
- data/lib/mcollective/application/playbook.rb +207 -0
- data/lib/mcollective/application/plugin.rb +106 -106
- data/lib/mcollective/application/rpc.rb +3 -108
- data/lib/mcollective/application/tasks.rb +425 -0
- data/lib/mcollective/applications.rb +11 -10
- data/lib/mcollective/audit/choria.rb +33 -0
- data/lib/mcollective/cache.rb +2 -4
- data/lib/mcollective/client.rb +11 -10
- data/lib/mcollective/config.rb +21 -34
- data/lib/mcollective/connector/base.rb +2 -1
- data/lib/mcollective/connector/nats.ddl +9 -0
- data/lib/mcollective/connector/nats.rb +450 -0
- data/lib/mcollective/data.rb +8 -3
- data/lib/mcollective/data/agent_data.rb +1 -1
- data/lib/mcollective/data/base.rb +6 -5
- data/lib/mcollective/data/bolt_task_data.ddl +90 -0
- data/lib/mcollective/data/bolt_task_data.rb +32 -0
- data/lib/mcollective/data/collective_data.rb +1 -1
- data/lib/mcollective/data/fact_data.rb +6 -6
- data/lib/mcollective/data/fstat_data.rb +2 -4
- data/lib/mcollective/data/result.rb +7 -2
- data/lib/mcollective/ddl/agentddl.rb +5 -17
- data/lib/mcollective/ddl/base.rb +10 -13
- data/lib/mcollective/discovery.rb +24 -39
- data/lib/mcollective/discovery/choria.ddl +11 -0
- data/lib/mcollective/discovery/choria.rb +223 -0
- data/lib/mcollective/discovery/flatfile.rb +7 -8
- data/lib/mcollective/discovery/mc.rb +2 -2
- data/lib/mcollective/discovery/stdin.rb +17 -18
- data/lib/mcollective/exceptions.rb +13 -0
- data/lib/mcollective/facts/base.rb +9 -9
- data/lib/mcollective/facts/yaml_facts.rb +12 -12
- data/lib/mcollective/generators.rb +3 -3
- data/lib/mcollective/generators/agent_generator.rb +3 -4
- data/lib/mcollective/generators/base.rb +14 -15
- data/lib/mcollective/generators/data_generator.rb +5 -6
- data/lib/mcollective/log.rb +2 -2
- data/lib/mcollective/logger/base.rb +3 -2
- data/lib/mcollective/logger/console_logger.rb +10 -10
- data/lib/mcollective/logger/file_logger.rb +7 -7
- data/lib/mcollective/logger/syslog_logger.rb +11 -15
- data/lib/mcollective/message.rb +8 -39
- data/lib/mcollective/monkey_patches.rb +2 -4
- data/lib/mcollective/optionparser.rb +2 -1
- data/lib/mcollective/pluginmanager.rb +3 -5
- data/lib/mcollective/pluginpackager.rb +1 -3
- data/lib/mcollective/pluginpackager/agent_definition.rb +3 -8
- data/lib/mcollective/pluginpackager/forge_packager.rb +7 -9
- data/lib/mcollective/pluginpackager/standard_definition.rb +1 -2
- data/lib/mcollective/registration/base.rb +18 -16
- data/lib/mcollective/rpc.rb +2 -4
- data/lib/mcollective/rpc/actionrunner.rb +16 -18
- data/lib/mcollective/rpc/agent.rb +26 -43
- data/lib/mcollective/rpc/audit.rb +1 -0
- data/lib/mcollective/rpc/client.rb +67 -85
- data/lib/mcollective/rpc/helpers.rb +55 -62
- data/lib/mcollective/rpc/progress.rb +2 -2
- data/lib/mcollective/rpc/reply.rb +17 -19
- data/lib/mcollective/rpc/request.rb +7 -5
- data/lib/mcollective/rpc/result.rb +6 -8
- data/lib/mcollective/rpc/stats.rb +49 -58
- data/lib/mcollective/security/base.rb +13 -56
- data/lib/mcollective/security/choria.rb +765 -0
- data/lib/mcollective/shell.rb +9 -4
- data/lib/mcollective/signer/base.rb +28 -0
- data/lib/mcollective/signer/choria.rb +185 -0
- data/lib/mcollective/ssl.rb +8 -6
- data/lib/mcollective/util.rb +58 -55
- data/lib/mcollective/util/bolt_support.rb +176 -0
- data/lib/mcollective/util/bolt_support/plan_runner.rb +167 -0
- data/lib/mcollective/util/bolt_support/task_result.rb +94 -0
- data/lib/mcollective/util/bolt_support/task_results.rb +128 -0
- data/lib/mcollective/util/choria.rb +946 -0
- data/lib/mcollective/util/indifferent_hash.rb +12 -0
- data/lib/mcollective/util/natswrapper.rb +242 -0
- data/lib/mcollective/util/playbook.rb +435 -0
- data/lib/mcollective/util/playbook/data_stores.rb +201 -0
- data/lib/mcollective/util/playbook/data_stores/base.rb +99 -0
- data/lib/mcollective/util/playbook/data_stores/consul_data_store.rb +88 -0
- data/lib/mcollective/util/playbook/data_stores/environment_data_store.rb +33 -0
- data/lib/mcollective/util/playbook/data_stores/etcd_data_store.rb +42 -0
- data/lib/mcollective/util/playbook/data_stores/file_data_store.rb +106 -0
- data/lib/mcollective/util/playbook/data_stores/shell_data_store.rb +103 -0
- data/lib/mcollective/util/playbook/inputs.rb +265 -0
- data/lib/mcollective/util/playbook/nodes.rb +207 -0
- data/lib/mcollective/util/playbook/nodes/mcollective_nodes.rb +86 -0
- data/lib/mcollective/util/playbook/nodes/pql_nodes.rb +40 -0
- data/lib/mcollective/util/playbook/nodes/shell_nodes.rb +55 -0
- data/lib/mcollective/util/playbook/nodes/terraform_nodes.rb +65 -0
- data/lib/mcollective/util/playbook/nodes/yaml_nodes.rb +47 -0
- data/lib/mcollective/util/playbook/playbook_logger.rb +47 -0
- data/lib/mcollective/util/playbook/puppet_logger.rb +51 -0
- data/lib/mcollective/util/playbook/report.rb +152 -0
- data/lib/mcollective/util/playbook/task_result.rb +55 -0
- data/lib/mcollective/util/playbook/tasks.rb +196 -0
- data/lib/mcollective/util/playbook/tasks/base.rb +45 -0
- data/lib/mcollective/util/playbook/tasks/graphite_event_task.rb +64 -0
- data/lib/mcollective/util/playbook/tasks/mcollective_task.rb +356 -0
- data/lib/mcollective/util/playbook/tasks/shell_task.rb +93 -0
- data/lib/mcollective/util/playbook/tasks/slack_task.rb +105 -0
- data/lib/mcollective/util/playbook/tasks/webhook_task.rb +136 -0
- data/lib/mcollective/util/playbook/template_util.rb +98 -0
- data/lib/mcollective/util/playbook/uses.rb +169 -0
- data/lib/mcollective/util/tasks_support.rb +751 -0
- data/lib/mcollective/util/tasks_support/cli.rb +260 -0
- data/lib/mcollective/util/tasks_support/default_formatter.rb +138 -0
- data/lib/mcollective/util/tasks_support/json_formatter.rb +108 -0
- data/lib/mcollective/validator.rb +6 -1
- data/lib/mcollective/validator/bolt_task_name_validator.ddl +7 -0
- data/lib/mcollective/validator/bolt_task_name_validator.rb +11 -0
- data/lib/mcollective/validator/length_validator.rb +1 -3
- metadata +65 -6
- data/lib/mcollective/application/describe_filter.rb +0 -87
- data/lib/mcollective/matcher.rb +0 -220
- data/lib/mcollective/matcher/parser.rb +0 -128
- 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
|