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,242 @@
|
|
1
|
+
require "nats/io/client"
|
2
|
+
|
3
|
+
module MCollective
|
4
|
+
module Util
|
5
|
+
# A wrapper class around the Pure Ruby NATS gem
|
6
|
+
#
|
7
|
+
# MCollective has some non compatible expectations about how
|
8
|
+
# message flow works such as having a blocking receive and publish
|
9
|
+
# method it calls when it likes, while typical flow is to pass
|
10
|
+
# a block and then callbacks will be called.
|
11
|
+
#
|
12
|
+
# This wrapper bridges the 2 worlds using ruby Queues to simulate the
|
13
|
+
# blocking receive expectation MCollective has thanks to its initial
|
14
|
+
# design around the Stomp gem.
|
15
|
+
class NatsWrapper
|
16
|
+
attr_reader :subscriptions, :received_queue
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@received_queue = Queue.new
|
20
|
+
@subscriptions = {}
|
21
|
+
@subscription_mutex = Mutex.new
|
22
|
+
@started = false
|
23
|
+
@client = NATS::IO::Client.new
|
24
|
+
end
|
25
|
+
|
26
|
+
# Has the NATS connection started
|
27
|
+
#
|
28
|
+
# @return [Boolean]
|
29
|
+
def started?
|
30
|
+
@started
|
31
|
+
end
|
32
|
+
|
33
|
+
# Is there a NATS client created
|
34
|
+
#
|
35
|
+
# @return [Boolean]
|
36
|
+
def has_client?
|
37
|
+
!!@client
|
38
|
+
end
|
39
|
+
|
40
|
+
# Retrieves the current connected server
|
41
|
+
#
|
42
|
+
# @return [String,nil]
|
43
|
+
def connected_server
|
44
|
+
return nil unless connected?
|
45
|
+
|
46
|
+
@client.connected_server
|
47
|
+
end
|
48
|
+
|
49
|
+
# Connection stats from the NATS gem
|
50
|
+
#
|
51
|
+
# @return [Hash]
|
52
|
+
def stats
|
53
|
+
return {} unless has_client?
|
54
|
+
|
55
|
+
@client.stats
|
56
|
+
end
|
57
|
+
|
58
|
+
# Client library flavour
|
59
|
+
#
|
60
|
+
# @return [String]
|
61
|
+
def client_flavour
|
62
|
+
"nats-pure"
|
63
|
+
end
|
64
|
+
|
65
|
+
# Client library version
|
66
|
+
#
|
67
|
+
# @return [String]
|
68
|
+
def client_version
|
69
|
+
NATS::IO::VERSION
|
70
|
+
end
|
71
|
+
|
72
|
+
# Connection options from the NATS gem
|
73
|
+
#
|
74
|
+
# @return [Hash]
|
75
|
+
def active_options
|
76
|
+
return {} unless has_client?
|
77
|
+
|
78
|
+
@client.options
|
79
|
+
end
|
80
|
+
|
81
|
+
# Is NATS connected
|
82
|
+
#
|
83
|
+
# @return [Boolean]
|
84
|
+
def connected?
|
85
|
+
has_client? && @client.connected?
|
86
|
+
end
|
87
|
+
|
88
|
+
# Does a backoff sleep up to 2 seconds
|
89
|
+
#
|
90
|
+
# @return [void]
|
91
|
+
def backoff_sleep
|
92
|
+
@backoffcount ||= 1
|
93
|
+
|
94
|
+
if @backoffcount >= 50
|
95
|
+
sleep(2)
|
96
|
+
else
|
97
|
+
sleep(0.04 * @backoffcount)
|
98
|
+
end
|
99
|
+
|
100
|
+
@backoffcount += 1
|
101
|
+
end
|
102
|
+
|
103
|
+
# Logs the NATS server pool for nats-pure
|
104
|
+
#
|
105
|
+
# The current server pool is dynamic as the NATS servers can announce
|
106
|
+
# new cluster members as they join the pool, little helper for logging
|
107
|
+
# the pool on major events
|
108
|
+
#
|
109
|
+
# @return [void]
|
110
|
+
def log_nats_pool
|
111
|
+
return unless has_client?
|
112
|
+
|
113
|
+
servers = @client.server_pool.map do |server|
|
114
|
+
server[:uri].to_s
|
115
|
+
end
|
116
|
+
|
117
|
+
Log.info("Current server pool: %s" % servers.join(", "))
|
118
|
+
end
|
119
|
+
|
120
|
+
# Starts the EM based NATS connection
|
121
|
+
#
|
122
|
+
# @param options [Hash] Options as per {#NATS::IO::Client#connect}
|
123
|
+
def start(options={})
|
124
|
+
# Client connects pretty much soon as it's initialized which is very early
|
125
|
+
# and some applications like 'request_cert' just doesnt need/want a client
|
126
|
+
# since for example there won't be SSL stuff yet, so if a application calls
|
127
|
+
# disconnect very early on this should avoid that chicken and egg
|
128
|
+
return if @force_Stop
|
129
|
+
|
130
|
+
@client.on_reconnect do
|
131
|
+
Log.warn("Reconnected after connection failure: %s" % connected_server)
|
132
|
+
log_nats_pool
|
133
|
+
@backoffcount = 1
|
134
|
+
end
|
135
|
+
|
136
|
+
@client.on_disconnect do |error|
|
137
|
+
if error
|
138
|
+
Log.warn("Disconnected from NATS: %s: %s" % [error.class, error.to_s])
|
139
|
+
else
|
140
|
+
Log.info("Disconnected from NATS for an unknown reason")
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
@client.on_error do |error|
|
145
|
+
Log.error("Error in NATS connection: %s: %s" % [error.class, error.to_s])
|
146
|
+
end
|
147
|
+
|
148
|
+
@client.on_close do
|
149
|
+
Log.info("Connection to NATS server closed")
|
150
|
+
end
|
151
|
+
|
152
|
+
begin
|
153
|
+
@client.connect(options)
|
154
|
+
rescue ClientTimeoutError
|
155
|
+
raise
|
156
|
+
rescue
|
157
|
+
Log.error("Error during initial NATS setup: %s: %s" % [$!.class, $!.message])
|
158
|
+
Log.debug($!.backtrace.join("\n\t"))
|
159
|
+
|
160
|
+
sleep 1
|
161
|
+
|
162
|
+
Log.error("Retrying NATS initial setup")
|
163
|
+
|
164
|
+
retry
|
165
|
+
end
|
166
|
+
|
167
|
+
sleep(0.01) until connected?
|
168
|
+
|
169
|
+
@started = true
|
170
|
+
|
171
|
+
nil
|
172
|
+
end
|
173
|
+
|
174
|
+
# Stops the NATS connection
|
175
|
+
def stop
|
176
|
+
@force_stop = true
|
177
|
+
@client.close
|
178
|
+
end
|
179
|
+
|
180
|
+
# Receives a message from the receive queue
|
181
|
+
#
|
182
|
+
# This will block until a message is available
|
183
|
+
#
|
184
|
+
# @return [String] received message
|
185
|
+
def receive
|
186
|
+
@received_queue.pop
|
187
|
+
end
|
188
|
+
|
189
|
+
# Public a message
|
190
|
+
#
|
191
|
+
# @param destination [String] the NATS destination
|
192
|
+
# @param payload [String] the string to publish
|
193
|
+
# @param reply [String] a reply destination
|
194
|
+
def publish(destination, payload, reply=nil)
|
195
|
+
server_state = "%s %s" % [connected? ? "connected" : "disconnected", @client.connected_server]
|
196
|
+
|
197
|
+
if reply
|
198
|
+
Log.debug("Publishing to %s reply to %s via %s" % [destination, reply, server_state])
|
199
|
+
else
|
200
|
+
Log.debug("Publishing to %s via %s" % [destination, server_state])
|
201
|
+
end
|
202
|
+
|
203
|
+
@client.publish(destination, payload, reply)
|
204
|
+
end
|
205
|
+
|
206
|
+
# Subscribes to a message source
|
207
|
+
#
|
208
|
+
# @param source_name [String]
|
209
|
+
# @param options [Hash] options as accepted by {NATS::IO::Client#subscribe}
|
210
|
+
def subscribe(source_name, options={})
|
211
|
+
@subscription_mutex.synchronize do
|
212
|
+
Log.debug("Subscribing to %s" % source_name)
|
213
|
+
|
214
|
+
unless @subscriptions.include?(source_name)
|
215
|
+
@subscriptions[source_name] = @client.subscribe(source_name, options) do |msg, _, _|
|
216
|
+
@received_queue << msg
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Unsubscribes from a message source
|
223
|
+
#
|
224
|
+
# @param source_name [String]
|
225
|
+
def unsubscribe(source_name)
|
226
|
+
@subscription_mutex.synchronize do
|
227
|
+
if @subscriptions.include?(source_name)
|
228
|
+
@client.unsubscribe(@subscriptions[source_name])
|
229
|
+
@subscriptions.delete(source_name)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# Test helper
|
235
|
+
#
|
236
|
+
# @private
|
237
|
+
def stub_client(client)
|
238
|
+
@client = client
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
@@ -0,0 +1,435 @@
|
|
1
|
+
require_relative "playbook/report"
|
2
|
+
require_relative "playbook/playbook_logger"
|
3
|
+
require_relative "playbook/puppet_logger"
|
4
|
+
require_relative "playbook/template_util"
|
5
|
+
require_relative "playbook/inputs"
|
6
|
+
require_relative "playbook/uses"
|
7
|
+
require_relative "playbook/nodes"
|
8
|
+
require_relative "playbook/tasks"
|
9
|
+
require_relative "playbook/data_stores"
|
10
|
+
|
11
|
+
module MCollective
|
12
|
+
module Util
|
13
|
+
class Playbook
|
14
|
+
include TemplateUtil
|
15
|
+
|
16
|
+
attr_accessor :input_data, :context
|
17
|
+
attr_reader :metadata, :report, :data_stores, :uses, :tasks, :logger
|
18
|
+
|
19
|
+
def initialize(loglevel=nil)
|
20
|
+
@loglevel = loglevel
|
21
|
+
|
22
|
+
@report = Report.new(self)
|
23
|
+
|
24
|
+
# @todo dear god this Camel_Snake horror but mcollective requires this
|
25
|
+
# Configures the main MCollective logger with our custom logger
|
26
|
+
@logger = Log.set_logger(Playbook_Logger.new(self))
|
27
|
+
|
28
|
+
@nodes = Nodes.new(self)
|
29
|
+
@tasks = Tasks.new(self)
|
30
|
+
@uses = Uses.new(self)
|
31
|
+
@inputs = Inputs.new(self)
|
32
|
+
@data_stores = DataStores.new(self)
|
33
|
+
@playbook = self
|
34
|
+
@playbook_data = {}
|
35
|
+
@input_data = {}
|
36
|
+
|
37
|
+
@metadata = {
|
38
|
+
"name" => nil,
|
39
|
+
"version" => nil,
|
40
|
+
"author" => nil,
|
41
|
+
"description" => nil,
|
42
|
+
"tags" => [],
|
43
|
+
"on_fail" => "fail",
|
44
|
+
"loglevel" => "info",
|
45
|
+
"run_as" => "choria=deployer"
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
# Sets a custom logger
|
50
|
+
#
|
51
|
+
# @param logger [Class] the logger instance to configure and use
|
52
|
+
def logger=(logger)
|
53
|
+
@logger = Log.set_logger(logger.new(self))
|
54
|
+
end
|
55
|
+
|
56
|
+
# Loads the playbook data and prepare the runner
|
57
|
+
#
|
58
|
+
# @param data [Hash] playbook data
|
59
|
+
# @return [Playbook]
|
60
|
+
def from_hash(data)
|
61
|
+
in_context("loading") do
|
62
|
+
@playbook_data = data
|
63
|
+
|
64
|
+
@metadata = {
|
65
|
+
"name" => data["name"],
|
66
|
+
"version" => data["version"],
|
67
|
+
"author" => data["author"],
|
68
|
+
"description" => data["description"],
|
69
|
+
"tags" => data.fetch("tags", []),
|
70
|
+
"on_fail" => data.fetch("on_fail", "fail"),
|
71
|
+
"loglevel" => data.fetch("loglevel", "info"),
|
72
|
+
"run_as" => data["run_as"]
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
set_logger_level
|
77
|
+
|
78
|
+
in_context("inputs") do
|
79
|
+
@inputs.from_hash(data.fetch("inputs", {}))
|
80
|
+
end
|
81
|
+
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
# Validate the playbook structure
|
86
|
+
#
|
87
|
+
# This is a pretty grim way to do this, ideally AIO would include some JSON Schema
|
88
|
+
# tools but they don't and I don't want to carray a dependency on that now
|
89
|
+
#
|
90
|
+
# @todo use JSON Schema
|
91
|
+
# @raise [StandardError] for invalid playbooks
|
92
|
+
def validate_configuration!
|
93
|
+
in_context("validation") do
|
94
|
+
failed = false
|
95
|
+
|
96
|
+
valid_keys = (@metadata.keys + ["uses", "inputs", "locks", "data_stores", "nodes", "tasks", "hooks", "macros", "$schema"])
|
97
|
+
invalid_keys = @playbook_data.keys - valid_keys
|
98
|
+
|
99
|
+
unless invalid_keys.empty?
|
100
|
+
Log.error("Invalid playbook data items %s found" % invalid_keys.join(", "))
|
101
|
+
failed = true
|
102
|
+
end
|
103
|
+
|
104
|
+
["name", "version", "author", "description"].each do |item|
|
105
|
+
unless @metadata[item]
|
106
|
+
Log.error("A playbook %s is needed" % item)
|
107
|
+
failed = true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
unless ["debug", "info", "warn", "error", "fatal"].include?(@metadata["loglevel"])
|
112
|
+
Log.error("Invalid log level %s, valid levels are debug, info, warn, error, fatal" % @metadata["loglevel"])
|
113
|
+
failed = true
|
114
|
+
end
|
115
|
+
|
116
|
+
unless PluginManager["security_plugin"].valid_callerid?(@metadata["run_as"])
|
117
|
+
Log.error("Invalid callerid %s" % @metadata["run_as"])
|
118
|
+
failed = true
|
119
|
+
end
|
120
|
+
|
121
|
+
["uses", "nodes", "hooks", "data_stores", "inputs"].each do |key|
|
122
|
+
next unless @playbook_data.include?(key)
|
123
|
+
next if @playbook_data[key].is_a?(Hash)
|
124
|
+
|
125
|
+
Log.error("%s should be a hash" % key)
|
126
|
+
failed = true
|
127
|
+
end
|
128
|
+
|
129
|
+
["locks", "tasks"].each do |key|
|
130
|
+
next unless @playbook_data.include?(key)
|
131
|
+
next if @playbook_data[key].is_a?(Array)
|
132
|
+
|
133
|
+
Log.error("%s should be a array" % key)
|
134
|
+
failed = true
|
135
|
+
end
|
136
|
+
|
137
|
+
raise("Playbook is not in a valid format") if failed
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def prepare
|
142
|
+
prepare_inputs
|
143
|
+
|
144
|
+
prepare_data_stores
|
145
|
+
save_input_data
|
146
|
+
|
147
|
+
obtain_playbook_locks
|
148
|
+
|
149
|
+
prepare_uses
|
150
|
+
prepare_nodes
|
151
|
+
prepare_tasks
|
152
|
+
end
|
153
|
+
|
154
|
+
# Saves data from any inputs that requested they be written to data stores
|
155
|
+
def save_input_data
|
156
|
+
@inputs.save_input_data
|
157
|
+
end
|
158
|
+
|
159
|
+
# Derives a playbook lock from a given lock
|
160
|
+
#
|
161
|
+
# If a lock is in the normal valid format of source/lock
|
162
|
+
# then it's assumed the user gave a full path and knows what
|
163
|
+
# she wants otherwise a path will be constructed using the
|
164
|
+
# playbook name
|
165
|
+
#
|
166
|
+
# @return [String]
|
167
|
+
def lock_path(lock)
|
168
|
+
lock =~ /^[a-zA-Z0-9\-_]+\/.+$/ ? lock : "%s/choria/locks/playbook/%s" % [lock, name]
|
169
|
+
end
|
170
|
+
|
171
|
+
# Obtains the playbook level locks
|
172
|
+
def obtain_playbook_locks
|
173
|
+
Array(@playbook_data["locks"]).each do |lock|
|
174
|
+
Log.info("Obtaining playbook lock %s" % [lock_path(lock)])
|
175
|
+
@data_stores.lock(lock_path(lock))
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Obtains the playbook level locks
|
180
|
+
def release_playbook_locks
|
181
|
+
Array(@playbook_data["locks"]).each do |lock|
|
182
|
+
Log.info("Releasing playbook lock %s" % [lock_path(lock)])
|
183
|
+
|
184
|
+
begin
|
185
|
+
@data_stores.release(lock_path(lock))
|
186
|
+
rescue
|
187
|
+
Log.warn("Lock %s could not be released, ignoring" % lock_path(lock))
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Runs the playbook
|
193
|
+
#
|
194
|
+
# @param inputs [Hash] input data
|
195
|
+
# @return [Hash] the playbook report
|
196
|
+
def run!(inputs)
|
197
|
+
success = false
|
198
|
+
validate_configuration!
|
199
|
+
|
200
|
+
begin
|
201
|
+
start_time = @report.start!
|
202
|
+
|
203
|
+
@input_data = inputs
|
204
|
+
|
205
|
+
in_context("pre") { Log.info("Starting playbook %s at %s" % [name, start_time]) }
|
206
|
+
|
207
|
+
prepare
|
208
|
+
|
209
|
+
success = in_context("run") { @tasks.run }
|
210
|
+
in_context("post") { Log.info("Done running playbook %s in %s" % [name, seconds_to_human(Integer(@report.elapsed_time))]) }
|
211
|
+
|
212
|
+
release_playbook_locks
|
213
|
+
rescue
|
214
|
+
msg = "Playbook %s failed: %s: %s" % [name, $!.class, $!.to_s]
|
215
|
+
|
216
|
+
Log.error(msg)
|
217
|
+
Log.debug($!.backtrace.join("\n\t"))
|
218
|
+
|
219
|
+
report.finalize(false, msg)
|
220
|
+
end
|
221
|
+
|
222
|
+
report.finalize(success)
|
223
|
+
end
|
224
|
+
|
225
|
+
# Playbook name as declared in metadata
|
226
|
+
#
|
227
|
+
# @return [String]
|
228
|
+
def name
|
229
|
+
metadata_item("name")
|
230
|
+
end
|
231
|
+
|
232
|
+
# Playbook version as declared in metadata
|
233
|
+
#
|
234
|
+
# @return [String]
|
235
|
+
def version
|
236
|
+
metadata_item("version")
|
237
|
+
end
|
238
|
+
|
239
|
+
def loglevel
|
240
|
+
@loglevel || metadata_item("loglevel") || "info"
|
241
|
+
end
|
242
|
+
|
243
|
+
def set_logger_level
|
244
|
+
@logger.set_level(loglevel.intern)
|
245
|
+
end
|
246
|
+
|
247
|
+
# Prepares the data sources from the plabook
|
248
|
+
def prepare_data_stores
|
249
|
+
in_context("pre.stores") { @data_stores.from_hash(t(@playbook_data["data_stores"] || {})).prepare }
|
250
|
+
end
|
251
|
+
|
252
|
+
# Prepares the inputs from the playbook
|
253
|
+
#
|
254
|
+
# @todo same pattern as prepare_uses and nodes
|
255
|
+
# @see Inputs#prepare
|
256
|
+
# @note this should be done first, before any uses, nodes or tasks are prepared
|
257
|
+
def prepare_inputs
|
258
|
+
in_context("prep.inputs") { @inputs.prepare(@input_data) }
|
259
|
+
end
|
260
|
+
|
261
|
+
# Prepares the uses clauses from the playbook
|
262
|
+
#
|
263
|
+
# @see Uses#prepare
|
264
|
+
def prepare_uses
|
265
|
+
in_context("prep.uses") { @uses.from_hash(t(@playbook_data["uses"] || {})).prepare }
|
266
|
+
end
|
267
|
+
|
268
|
+
# Prepares the ode lists from the Playbook
|
269
|
+
#
|
270
|
+
# @see Nodes#prepare
|
271
|
+
def prepare_nodes
|
272
|
+
in_context("prep.nodes") { @nodes.from_hash(t(@playbook_data["nodes"] || {})).prepare }
|
273
|
+
end
|
274
|
+
|
275
|
+
# Prepares the tasks lists `tasks` and `hooks` from the Playbook data
|
276
|
+
#
|
277
|
+
# @see Tasks#prepare
|
278
|
+
def prepare_tasks
|
279
|
+
# we lazy template parse these so that they might refer to run time
|
280
|
+
# state via the template system - like for example in a post task you
|
281
|
+
# might want to reference properties of another rpc request
|
282
|
+
in_context("prep.tasks") do
|
283
|
+
@tasks.from_hash(@playbook_data["tasks"] || [])
|
284
|
+
@tasks.from_hash(@playbook_data["hooks"] || {})
|
285
|
+
@tasks.prepare
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
# Validates agent versions on nodes
|
290
|
+
#
|
291
|
+
# @param agents [Hash] a hash of agent names and nodes that uses that agent
|
292
|
+
# @raise [StandardError] on failure
|
293
|
+
def validate_agents(agents)
|
294
|
+
@uses.validate_agents(agents)
|
295
|
+
end
|
296
|
+
|
297
|
+
# Retrieves an item from the metadata
|
298
|
+
#
|
299
|
+
# @param item [name, version, author, description, tags, on_fail, loglevel, run_as]
|
300
|
+
# @return [Object] the corresponding item from `@metadata`
|
301
|
+
# @raise [StandardError] for invalid metadata items
|
302
|
+
def metadata_item(item)
|
303
|
+
if @metadata.include?(item)
|
304
|
+
@metadata[item]
|
305
|
+
else
|
306
|
+
raise("Unknown playbook metadata %s" % item)
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
# (see Nodes#[])
|
311
|
+
def discovered_nodes(nodeset)
|
312
|
+
@nodes[nodeset].clone
|
313
|
+
end
|
314
|
+
|
315
|
+
# A list of known node sets
|
316
|
+
#
|
317
|
+
# @return [Array<String>]
|
318
|
+
def nodes
|
319
|
+
@nodes.keys
|
320
|
+
end
|
321
|
+
|
322
|
+
# (see Inputs#[])
|
323
|
+
def input_value(input)
|
324
|
+
@inputs[input]
|
325
|
+
end
|
326
|
+
|
327
|
+
# A list of known input keys
|
328
|
+
#
|
329
|
+
# @return [Array<String>]
|
330
|
+
def inputs
|
331
|
+
@inputs.keys
|
332
|
+
end
|
333
|
+
|
334
|
+
# List of known input names that have dynamic values
|
335
|
+
#
|
336
|
+
# @return [Array<String>]
|
337
|
+
def dynamic_inputs
|
338
|
+
@inputs.dynamic_keys
|
339
|
+
end
|
340
|
+
|
341
|
+
# List of known input names that have static values
|
342
|
+
#
|
343
|
+
# @return [Array<String>]
|
344
|
+
def static_inputs
|
345
|
+
@inputs.static_keys
|
346
|
+
end
|
347
|
+
|
348
|
+
# Looks up a proeprty of the previous task
|
349
|
+
#
|
350
|
+
# @param property [success, msg, message, data, description]
|
351
|
+
# @return [Object]
|
352
|
+
def previous_task(property)
|
353
|
+
if property == "success"
|
354
|
+
return false unless previous_task_result && previous_task_result.ran
|
355
|
+
|
356
|
+
previous_task_result.success
|
357
|
+
elsif ["msg", "message"].include?(property)
|
358
|
+
return "No previous task were found" unless previous_task_result
|
359
|
+
return "Previous task did not run" unless previous_task_result.ran
|
360
|
+
|
361
|
+
previous_task_result.msg
|
362
|
+
elsif property == "data"
|
363
|
+
return [] unless previous_task_result && previous_task_result.ran
|
364
|
+
|
365
|
+
previous_task_result.data || []
|
366
|
+
elsif property == "description"
|
367
|
+
return "No previous task were found" unless previous_task_result
|
368
|
+
|
369
|
+
previous_task_result.task[:description]
|
370
|
+
elsif property == "runtime"
|
371
|
+
return 0 unless previous_task_result && previous_task_result.ran
|
372
|
+
|
373
|
+
previous_task_result.run_time.round(2)
|
374
|
+
else
|
375
|
+
raise("Cannot retrieve %s for the last task outcome" % property)
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
# All the task results
|
380
|
+
#
|
381
|
+
# @return [Array<TaskResult>]
|
382
|
+
def task_results
|
383
|
+
@tasks.results
|
384
|
+
end
|
385
|
+
|
386
|
+
# Find the last result from the tasks ran
|
387
|
+
#
|
388
|
+
# @return [TaskResult,nil]
|
389
|
+
def previous_task_result
|
390
|
+
task_results.last
|
391
|
+
end
|
392
|
+
|
393
|
+
# Adds the CLI options for an application based on the playbook inputs
|
394
|
+
#
|
395
|
+
# @see Inputs#add_cli_options
|
396
|
+
# @param application [MCollective::Application]
|
397
|
+
# @param set_required [Boolean]
|
398
|
+
def add_cli_options(application, set_required=false)
|
399
|
+
@inputs.add_cli_options(application, set_required)
|
400
|
+
end
|
401
|
+
|
402
|
+
def in_context(context)
|
403
|
+
old_context = @context
|
404
|
+
@context = context
|
405
|
+
|
406
|
+
yield
|
407
|
+
ensure
|
408
|
+
@context = old_context
|
409
|
+
end
|
410
|
+
|
411
|
+
def seconds_to_human(seconds)
|
412
|
+
days = seconds / 86400
|
413
|
+
seconds -= 86400 * days
|
414
|
+
|
415
|
+
hours = seconds / 3600
|
416
|
+
seconds -= 3600 * hours
|
417
|
+
|
418
|
+
minutes = seconds / 60
|
419
|
+
seconds -= 60 * minutes
|
420
|
+
|
421
|
+
if days > 1
|
422
|
+
"%d days %d hours %d minutes %02d seconds" % [days, hours, minutes, seconds]
|
423
|
+
elsif days == 1
|
424
|
+
"%d day %d hours %d minutes %02d seconds" % [days, hours, minutes, seconds]
|
425
|
+
elsif hours > 0
|
426
|
+
"%d hours %d minutes %02d seconds" % [hours, minutes, seconds]
|
427
|
+
elsif minutes > 0
|
428
|
+
"%d minutes %02d seconds" % [minutes, seconds]
|
429
|
+
else
|
430
|
+
"%02d seconds" % seconds
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|