choria-mcorpc-support 2.22.1 → 2.23.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mcollective.rb +1 -1
  3. data/lib/mcollective/agent/bolt_tasks.ddl +235 -0
  4. data/lib/mcollective/agent/bolt_tasks.json +347 -0
  5. data/lib/mcollective/agent/bolt_tasks.rb +176 -0
  6. data/lib/mcollective/agent/choria_util.ddl +152 -0
  7. data/lib/mcollective/agent/choria_util.json +244 -0
  8. data/lib/mcollective/agent/rpcutil.ddl +7 -3
  9. data/lib/mcollective/agent/rpcutil.json +333 -0
  10. data/lib/mcollective/agent/scout.ddl +169 -0
  11. data/lib/mcollective/agent/scout.json +224 -0
  12. data/lib/mcollective/agents.rb +7 -6
  13. data/lib/mcollective/aggregate.rb +4 -4
  14. data/lib/mcollective/aggregate/average.rb +2 -2
  15. data/lib/mcollective/aggregate/base.rb +2 -2
  16. data/lib/mcollective/aggregate/result.rb +3 -3
  17. data/lib/mcollective/aggregate/result/collection_result.rb +2 -2
  18. data/lib/mcollective/aggregate/result/numeric_result.rb +2 -2
  19. data/lib/mcollective/aggregate/sum.rb +2 -2
  20. data/lib/mcollective/aggregate/summary.rb +3 -4
  21. data/lib/mcollective/application.rb +57 -21
  22. data/lib/mcollective/application/choria.rb +249 -0
  23. data/lib/mcollective/application/completion.rb +6 -6
  24. data/lib/mcollective/application/describe_filter.rb +20 -20
  25. data/lib/mcollective/application/facts.rb +11 -11
  26. data/lib/mcollective/application/federation.rb +239 -0
  27. data/lib/mcollective/application/find.rb +4 -4
  28. data/lib/mcollective/application/help.rb +3 -3
  29. data/lib/mcollective/application/inventory.rb +3 -341
  30. data/lib/mcollective/application/ping.rb +3 -77
  31. data/lib/mcollective/application/playbook.rb +207 -0
  32. data/lib/mcollective/application/plugin.rb +106 -106
  33. data/lib/mcollective/application/rpc.rb +3 -108
  34. data/lib/mcollective/application/tasks.rb +416 -0
  35. data/lib/mcollective/applications.rb +11 -10
  36. data/lib/mcollective/audit/choria.rb +33 -0
  37. data/lib/mcollective/cache.rb +2 -4
  38. data/lib/mcollective/client.rb +11 -10
  39. data/lib/mcollective/config.rb +21 -26
  40. data/lib/mcollective/connector/base.rb +2 -1
  41. data/lib/mcollective/connector/nats.ddl +9 -0
  42. data/lib/mcollective/connector/nats.rb +450 -0
  43. data/lib/mcollective/data.rb +8 -3
  44. data/lib/mcollective/data/agent_data.rb +1 -1
  45. data/lib/mcollective/data/base.rb +6 -5
  46. data/lib/mcollective/data/bolt_task_data.ddl +90 -0
  47. data/lib/mcollective/data/bolt_task_data.rb +32 -0
  48. data/lib/mcollective/data/collective_data.rb +1 -1
  49. data/lib/mcollective/data/fact_data.rb +6 -6
  50. data/lib/mcollective/data/fstat_data.rb +2 -4
  51. data/lib/mcollective/data/result.rb +7 -2
  52. data/lib/mcollective/ddl/agentddl.rb +5 -17
  53. data/lib/mcollective/ddl/base.rb +10 -13
  54. data/lib/mcollective/discovery.rb +12 -26
  55. data/lib/mcollective/discovery/choria.ddl +11 -0
  56. data/lib/mcollective/discovery/choria.rb +223 -0
  57. data/lib/mcollective/discovery/flatfile.rb +7 -8
  58. data/lib/mcollective/discovery/mc.rb +2 -2
  59. data/lib/mcollective/discovery/stdin.rb +17 -18
  60. data/lib/mcollective/exceptions.rb +13 -0
  61. data/lib/mcollective/facts/base.rb +9 -9
  62. data/lib/mcollective/facts/yaml_facts.rb +12 -12
  63. data/lib/mcollective/generators.rb +3 -3
  64. data/lib/mcollective/generators/agent_generator.rb +3 -4
  65. data/lib/mcollective/generators/base.rb +14 -15
  66. data/lib/mcollective/generators/data_generator.rb +5 -6
  67. data/lib/mcollective/log.rb +2 -2
  68. data/lib/mcollective/logger/base.rb +3 -2
  69. data/lib/mcollective/logger/console_logger.rb +10 -10
  70. data/lib/mcollective/logger/file_logger.rb +7 -7
  71. data/lib/mcollective/logger/syslog_logger.rb +11 -15
  72. data/lib/mcollective/matcher.rb +14 -14
  73. data/lib/mcollective/matcher/parser.rb +31 -41
  74. data/lib/mcollective/matcher/scanner.rb +69 -74
  75. data/lib/mcollective/message.rb +10 -17
  76. data/lib/mcollective/monkey_patches.rb +2 -4
  77. data/lib/mcollective/optionparser.rb +1 -0
  78. data/lib/mcollective/pluginmanager.rb +3 -5
  79. data/lib/mcollective/pluginpackager.rb +1 -3
  80. data/lib/mcollective/pluginpackager/agent_definition.rb +3 -8
  81. data/lib/mcollective/pluginpackager/forge_packager.rb +7 -9
  82. data/lib/mcollective/pluginpackager/standard_definition.rb +1 -2
  83. data/lib/mcollective/registration/base.rb +18 -16
  84. data/lib/mcollective/rpc.rb +2 -4
  85. data/lib/mcollective/rpc/actionrunner.rb +16 -18
  86. data/lib/mcollective/rpc/agent.rb +26 -43
  87. data/lib/mcollective/rpc/audit.rb +1 -0
  88. data/lib/mcollective/rpc/client.rb +67 -85
  89. data/lib/mcollective/rpc/helpers.rb +55 -62
  90. data/lib/mcollective/rpc/progress.rb +2 -2
  91. data/lib/mcollective/rpc/reply.rb +17 -19
  92. data/lib/mcollective/rpc/request.rb +7 -5
  93. data/lib/mcollective/rpc/result.rb +6 -8
  94. data/lib/mcollective/rpc/stats.rb +49 -58
  95. data/lib/mcollective/security/base.rb +29 -36
  96. data/lib/mcollective/security/choria.rb +765 -0
  97. data/lib/mcollective/shell.rb +9 -4
  98. data/lib/mcollective/signer/base.rb +28 -0
  99. data/lib/mcollective/signer/choria.rb +185 -0
  100. data/lib/mcollective/ssl.rb +8 -6
  101. data/lib/mcollective/util.rb +52 -53
  102. data/lib/mcollective/util/bolt_support.rb +176 -0
  103. data/lib/mcollective/util/bolt_support/plan_runner.rb +167 -0
  104. data/lib/mcollective/util/bolt_support/task_result.rb +94 -0
  105. data/lib/mcollective/util/bolt_support/task_results.rb +128 -0
  106. data/lib/mcollective/util/choria.rb +1103 -0
  107. data/lib/mcollective/util/indifferent_hash.rb +12 -0
  108. data/lib/mcollective/util/natswrapper.rb +242 -0
  109. data/lib/mcollective/util/playbook.rb +435 -0
  110. data/lib/mcollective/util/playbook/data_stores.rb +201 -0
  111. data/lib/mcollective/util/playbook/data_stores/base.rb +99 -0
  112. data/lib/mcollective/util/playbook/data_stores/consul_data_store.rb +88 -0
  113. data/lib/mcollective/util/playbook/data_stores/environment_data_store.rb +33 -0
  114. data/lib/mcollective/util/playbook/data_stores/etcd_data_store.rb +42 -0
  115. data/lib/mcollective/util/playbook/data_stores/file_data_store.rb +106 -0
  116. data/lib/mcollective/util/playbook/data_stores/shell_data_store.rb +103 -0
  117. data/lib/mcollective/util/playbook/inputs.rb +265 -0
  118. data/lib/mcollective/util/playbook/nodes.rb +207 -0
  119. data/lib/mcollective/util/playbook/nodes/mcollective_nodes.rb +86 -0
  120. data/lib/mcollective/util/playbook/nodes/pql_nodes.rb +40 -0
  121. data/lib/mcollective/util/playbook/nodes/shell_nodes.rb +55 -0
  122. data/lib/mcollective/util/playbook/nodes/terraform_nodes.rb +65 -0
  123. data/lib/mcollective/util/playbook/nodes/yaml_nodes.rb +47 -0
  124. data/lib/mcollective/util/playbook/playbook_logger.rb +47 -0
  125. data/lib/mcollective/util/playbook/puppet_logger.rb +51 -0
  126. data/lib/mcollective/util/playbook/report.rb +152 -0
  127. data/lib/mcollective/util/playbook/task_result.rb +55 -0
  128. data/lib/mcollective/util/playbook/tasks.rb +196 -0
  129. data/lib/mcollective/util/playbook/tasks/base.rb +45 -0
  130. data/lib/mcollective/util/playbook/tasks/graphite_event_task.rb +64 -0
  131. data/lib/mcollective/util/playbook/tasks/mcollective_task.rb +356 -0
  132. data/lib/mcollective/util/playbook/tasks/shell_task.rb +93 -0
  133. data/lib/mcollective/util/playbook/tasks/slack_task.rb +105 -0
  134. data/lib/mcollective/util/playbook/tasks/webhook_task.rb +136 -0
  135. data/lib/mcollective/util/playbook/template_util.rb +98 -0
  136. data/lib/mcollective/util/playbook/uses.rb +169 -0
  137. data/lib/mcollective/util/tasks_support.rb +733 -0
  138. data/lib/mcollective/util/tasks_support/cli.rb +260 -0
  139. data/lib/mcollective/util/tasks_support/default_formatter.rb +138 -0
  140. data/lib/mcollective/util/tasks_support/json_formatter.rb +108 -0
  141. data/lib/mcollective/validator.rb +6 -1
  142. data/lib/mcollective/validator/bolt_task_name_validator.ddl +7 -0
  143. data/lib/mcollective/validator/bolt_task_name_validator.rb +11 -0
  144. data/lib/mcollective/validator/length_validator.rb +1 -3
  145. metadata +67 -4
@@ -0,0 +1,12 @@
1
+ module MCollective
2
+ module Util
3
+ class IndifferentHash < Hash
4
+ def [](key)
5
+ return super if key?(key)
6
+ return self[key.to_s] if key.is_a?(Symbol)
7
+
8
+ super
9
+ end
10
+ end
11
+ end
12
+ end
@@ -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