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,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
|