choria-mcorpc-support 2.20.8 → 2.23.0.pre
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 +1 -1
- data/lib/mcollective/agent/bolt_tasks.ddl +235 -0
- data/lib/mcollective/agent/bolt_tasks.json +347 -0
- data/lib/mcollective/agent/bolt_tasks.rb +176 -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 +7 -3
- 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 +249 -0
- data/lib/mcollective/application/completion.rb +6 -6
- data/lib/mcollective/application/describe_filter.rb +20 -20
- data/lib/mcollective/application/facts.rb +19 -11
- data/lib/mcollective/application/federation.rb +239 -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 +3 -77
- 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 +416 -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 +11 -14
- data/lib/mcollective/discovery.rb +12 -26
- 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/matcher.rb +14 -14
- data/lib/mcollective/matcher/parser.rb +31 -41
- data/lib/mcollective/matcher/scanner.rb +69 -74
- data/lib/mcollective/message.rb +10 -17
- data/lib/mcollective/monkey_patches.rb +2 -4
- data/lib/mcollective/optionparser.rb +1 -0
- data/lib/mcollective/pluginmanager.rb +3 -5
- data/lib/mcollective/pluginpackager.rb +1 -3
- data/lib/mcollective/pluginpackager/agent_definition.rb +10 -11
- 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 +29 -36
- 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 +1103 -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 +733 -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 +8 -3
- 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
- data/lib/mcollective/validator/typecheck_validator.rb +4 -0
- metadata +67 -4
@@ -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
|