diff --git a/lib/mcollective/agent/bolt_task.rb b/lib/mcollective/agent/bolt_task.rb index 6ff3f39..5aec6ec 100644 --- a/lib/mcollective/agent/bolt_task.rb +++ b/lib/mcollective/agent/bolt_task.rb @@ -4,10 +4,14 @@ module MCollective module Agent class Bolt_task < RPC::Agent + activate_when do + Util::Choria.new.tasks_support.tasks_compatible? + end + action "download" do reply[:downloads] = 0 - tasks = Util::Choria.new.tasks_support + tasks = support_factory reply.fail!("Received empty or invalid task file specification", 3) unless request[:files] @@ -23,7 +27,7 @@ class Bolt_task < RPC::Agent end action "run_and_wait" do - tasks = Util::Choria.new.tasks_support + tasks = support_factory reply[:task_id] = request.uniqid @@ -53,7 +57,7 @@ class Bolt_task < RPC::Agent end action "run_no_wait" do - tasks = Util::Choria.new.tasks_support + tasks = support_factory reply[:task_id] = request.uniqid @@ -72,7 +76,7 @@ class Bolt_task < RPC::Agent end action "task_status" do - tasks = Util::Choria.new.tasks_support + tasks = support_factory begin status = tasks.task_status(request[:task_id]) @@ -87,24 +91,29 @@ class Bolt_task < RPC::Agent end end - # Performs an additional authorization and audit line using the task name as action + def support_factory + Util::Choria.new.tasks_support + end + + # Performs an additional authorization and audit using the task name as action def before_processing_hook(msg, connection) - if respond_to?("authorization_hook") - original_action = request.action - task = request[:task] - - begin - Log.debug(request) - if ["run_and_wait", "run_no_wait"].include?(original_action) && task - request.action = task - authorization_hook(request) - audit_request(request, connection) + original_action = request.action + task = request[:task] + + begin + if ["run_and_wait", "run_no_wait"].include?(original_action) && task + request.action = task + + begin + authorization_hook(request) if respond_to?("authorization_hook") + rescue + raise(RPCAborted, "You are not authorized to run Bolt Task %s" % task) end - rescue - raise(RPCAborted, "You are not authorized to run Bolt Task %s" % task) - ensure - request.action = original_action + + audit_request(request, connection) end + ensure + request.action = original_action end end diff --git a/lib/mcollective/data/bolt_task_data.ddl b/lib/mcollective/data/bolt_task_data.ddl new file mode 100644 index 0000000..e98dca0 --- /dev/null +++ b/lib/mcollective/data/bolt_task_data.ddl @@ -0,0 +1,90 @@ +metadata :name => "bolt_task", + :description => "Information about past Bolt Task", + :author => "R.I.Pienaar ", + :license => "Apache-2.0", + :version => "0.0.1", + :url => "https://choria.io", + :timeout => 1 + +usage <<-EOU +This data plugin let you extract information about a previously +run Bolt Task for use in discovery and elsewhere. + +To run a task on nodes where one previously failed: + + mco tasks run myapp::update -S "bolt_task(ae561842dc7d5a9dae94f766dfb3d4c8).exitcode > 0" +EOU + +dataquery :description => "Puppet Bolt Task state" do + input :query, + :prompt => "Task ID", + :description => "The Task ID to retrieve", + :type => :string, + :validation => '^[a-z,0-9]{32}$', + :maxlength => 32 + + output :known, + :description => "If this is a known task on this node", + :display_as => "Known Task", + :default => false + + output :spool, + :description => "Where on disk the task status is stored", + :display_as => "Spool", + :default => "" + + output :task, + :description => "The name of the task that was run", + :display_as => "Task", + :default => "" + + output :caller, + :description => "The user who invoked the task", + :display_as => "Invoked by", + :default => "" + + output :stdout, + :description => "The STDOUT output from the task", + :display_as => "STDOUT", + :default => "" + + output :stderr, + :description => "The STDERR output from the task", + :display_as => "STDERR", + :default => "" + + output :exitcode, + :description => "The exitcode from the task", + :display_as => "Exit Code", + :default => 127 + + output :runtime, + :description => "How long the task took to run", + :display_as => "Runtime", + :default => 0.0 + + output :start_time, + :description => "When the task was started, seconds since 1970 in UTC time", + :display_as => "Start Time", + :default => 0 + + output :wrapper_spawned, + :description => "Did the wrapper start successfully", + :display_as => "Wrapper Spawned", + :default => false + + output :wrapper_error, + :description => "Error output from the wrapper command", + :display_as => "Wrapper Error", + :default => "" + + output :wrapper_pid, + :description => "The PID of the wrapper that runs the task", + :display_as => "Wrapper PID", + :default => -1 + + output :completed, + :description => "Did the task complete running", + :display_as => "Completed", + :default => false +end diff --git a/lib/mcollective/data/bolt_task_data.rb b/lib/mcollective/data/bolt_task_data.rb new file mode 100644 index 0000000..d12390c --- /dev/null +++ b/lib/mcollective/data/bolt_task_data.rb @@ -0,0 +1,31 @@ +module MCollective + module Data + class Bolt_task_data < Base + activate_when do + Util::Choria.new.tasks_support.tasks_compatible? + end + + query do |taskid| + tasks = Util::Choria.new.tasks_support + + begin + status = tasks.task_status(taskid) + + result[:known] = true + + if status["task"] + tasks.task_status(taskid).each do |item, value| + value = value.utc.to_i if value.is_a?(Time) + + result[item.intern] = value + end + + result[:start_time] = result[:start_time].to_i + end + rescue + Log.debug("Task %s was not found, returning default data" % taskid) + end + end + end + end +end diff --git a/lib/mcollective/util/tasks_support.rb b/lib/mcollective/util/tasks_support.rb index be34bb6..3c4f58a 100644 --- a/lib/mcollective/util/tasks_support.rb +++ b/lib/mcollective/util/tasks_support.rb @@ -12,6 +12,14 @@ def initialize(choria, cache_dir=nil) @cache_dir = cache_dir || @choria.get_option("choria.tasks_cache") end + # Determines if a machine is compatible with running bolt + # + # @note this should check for a compatible version of Puppet more + # @return [Boolean] + def tasks_compatible? + File.exist?(wrapper_path) && File.executable?(wrapper_path) + end + # Path to binaries like wrappers etc def bin_path if Util.windows? @@ -453,8 +461,7 @@ def file_sha256(file_path) # Determines the file size of a specific file # # @param file_path [String] a full path to the file to check - # @return [Integer] bytes - # @raise [StandardError] when the file does not exist + # @return [Integer] bytes, -1 when the file does not exist def file_size(file_path) File.stat(file_path).size rescue diff --git a/module/data/plugin.yaml b/module/data/plugin.yaml index 43dd7e6..3188dbc 100644 --- a/module/data/plugin.yaml +++ b/module/data/plugin.yaml @@ -12,6 +12,7 @@ mcollective_choria::server_files: - agent/bolt_task.rb - agent/choria_util.rb - audit/choria.rb + - data/bolt_task_data.rb - registration/choria.rb mcollective_choria::client_files: - application/choria.rb @@ -59,6 +60,7 @@ mcollective_choria::common_files: - agent/choria_util.ddl - connector/nats.ddl - connector/nats.rb + - data/bolt_task_data.ddl - security/choria.rb - util/federation_broker.rb - util/federation_broker/base.rb diff --git a/spec/unit/mcollective/util/tasks_support_spec.rb b/spec/unit/mcollective/util/tasks_support_spec.rb index 9685d3c..e01ced3 100644 --- a/spec/unit/mcollective/util/tasks_support_spec.rb +++ b/spec/unit/mcollective/util/tasks_support_spec.rb @@ -23,6 +23,24 @@ module Util FileUtils.rm_rf("/tmp/tasks-cache-#{$$}") end + describe "#tasks_compatible?" do + it "should report compatible only when the wrapper exist and is executable" do + ts.stubs(:wrapper_path).returns("/nonexisting/wrapper") + + File.expects(:exist?).with("/nonexisting/wrapper").returns(false) + File.expects(:executable?).with("/nonexisting/wrapper").never + expect(ts.tasks_compatible?).to be(false) + + File.expects(:exist?).with("/nonexisting/wrapper").returns(true) + File.expects(:executable?).with("/nonexisting/wrapper").returns(false) + expect(ts.tasks_compatible?).to be(false) + + File.expects(:exist?).with("/nonexisting/wrapper").returns(true) + File.expects(:executable?).with("/nonexisting/wrapper").returns(true) + expect(ts.tasks_compatible?).to be(true) + end + end + describe "#task_runtime" do it "should support succesfully completed tasks" do ts.stubs(:request_spooldir).returns("spec/fixtures/tasks/completed_spool")