Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions app/controllers/api/v2/job_invocations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,14 @@ def output
param_group :search_and_pagination, ::Api::V2::BaseController
add_scoped_search_description_for(JobInvocation)
param :id, :identifier, :required => true
param :include_permissions, :bool, :required => false, :desc => N_('Include per-host task and permission data in the response')
def hosts
set_hosts_and_template_invocations
@total = @hosts.size
@hosts = @hosts.search_for(params[:search], :order => params[:order]).paginate(:page => params[:page], :per_page => params[:per_page])
@subtotal = @hosts.total_entries
set_statuses_and_smart_proxies
set_tasks_and_permissions if Foreman::Cast.to_bool(params[:include_permissions])
if params[:awaiting]
@hosts = @hosts.select { |host| @host_statuses[host.id] == 'N/A' }
end
Expand Down Expand Up @@ -321,15 +323,35 @@ def set_statuses_and_smart_proxies
template_invocations = @template_invocations.where(host_id: @hosts.select(:id))
.includes(:run_host_job_task).to_a
hosts = @hosts.to_a
template_invocations_by_host_id = template_invocations.index_by(&:host_id)
@template_invocations_by_host_id = template_invocations.index_by(&:host_id)
@host_statuses = hosts.to_h do |host|
template_invocation = template_invocations_by_host_id[host.id]
template_invocation = @template_invocations_by_host_id[host.id]
task = template_invocation.try(:run_host_job_task)
[host.id, template_invocation_status(task, @job_invocation.task)]
end
@smart_proxy_id = template_invocations.to_h { |ti| [ti.host_id, ti.smart_proxy_id] }
@smart_proxy_name = template_invocations.to_h { |ti| [ti.host_id, ti.smart_proxy_name] }
end

def set_tasks_and_permissions
can_view_tasks = User.current.can?(:view_foreman_tasks)
can_cancel = authorized_for(:permission => :cancel_job_invocations, :auth_object => @job_invocation)
can_create = authorized_for(controller: :job_invocations, action: :create)
can_execute_on_infra = User.current.can?(:execute_jobs_on_infrastructure_hosts)

hosts = @hosts.to_a
@task_by_host = hosts.to_h do |host|
task = @template_invocations_by_host_id[host.id].try(:run_host_job_task)
[host.id, task ? { :id => task.id, :cancellable => task.try(:cancellable?) || false } : nil]
end
@permissions_by_host = hosts.to_h do |host|
[host.id, {
:view_foreman_tasks => can_view_tasks,
:cancel_job_invocations => can_cancel,
:execute_jobs => can_create && (!host.infrastructure_host? || can_execute_on_infra),
}]
end
end
end
end
end
29 changes: 1 addition & 28 deletions app/controllers/job_invocations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -149,33 +149,6 @@ def preview_job_invocations_per_host
render :json => {:job_invocations => job_invocations}
end

def list_jobs_hosts
@job_invocation = resource_base.find(params[:id])
hosts = @job_invocation.targeting.hosts.authorized(:view_hosts, Host)
hosts = hosts.search_for(params[:search])
template_invocations_task_by_hosts = {}
hosts.each do |host|
template_invocation = @job_invocation.template_invocations.find { |template_inv| template_inv.host_id == host.id }
next unless template_invocation
template_invocation_task = template_invocation.run_host_job_task
template_invocations_task_by_hosts[host.id] =
{
:host_name => host.name,
:id => host.id,
:task => template_invocation_task.attributes.merge({cancellable: template_invocation_task.cancellable? }),
:permissions => {
:view_foreman_tasks => authorized_for(:permission => :view_foreman_tasks, :auth_object => template_invocation_task),
:cancel_job_invocations => authorized_for(:permission => :cancel_job_invocations, :auth_object => @job_invocation),
:execute_jobs => authorized_for(controller: :job_invocations, action: :create) && (!host.infrastructure_host? || User.current.can?(:execute_jobs_on_infrastructure_hosts)),
},
}
end

render json: {
:template_invocations_task_by_hosts => template_invocations_task_by_hosts,
}
end

private

def action_permission
Expand All @@ -186,7 +159,7 @@ def action_permission
'create'
when 'cancel'
'cancel'
when 'chart', 'preview_job_invocations_per_host', 'list_jobs_hosts'
when 'chart', 'preview_job_invocations_per_host'
'view'
else
super
Expand Down
8 changes: 8 additions & 0 deletions app/views/api/v2/job_invocations/hosts.json.rabl
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,11 @@ end
node :smart_proxy_name do |host|
@smart_proxy_name[host.id]
end

node(:task, :if => ->(_host) { @task_by_host }) do |host|
@task_by_host[host.id]
end

node(:permissions, :if => ->(_host) { @permissions_by_host }) do |host|
@permissions_by_host[host.id]
end
1 change: 1 addition & 0 deletions app/views/api/v2/job_invocations/main.json.rabl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ end

child :task do
attributes :id, :state, :started_at
node(:cancellable) { |task| task.try(:cancellable?) }
end

if @template_invocations
Expand Down
1 change: 0 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
get 'auto_complete_search'
end
member do
get 'hosts', to: 'job_invocations#list_jobs_hosts'
post 'cancel'
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/foreman_remote_execution/plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,9 @@
permission :lock_job_templates, { :job_templates => [:lock, :unlock] }, :resource_type => 'JobTemplate'
permission :create_job_invocations, { :job_invocations => [:new, :create, :legacy_create, :refresh, :rerun, :preview_hosts],
'api/v2/job_invocations' => [:create, :rerun] }, :resource_type => 'JobInvocation'
permission :view_job_invocations, { :job_invocations => [:index, :chart, :show, :auto_complete_search, :preview_job_invocations_per_host, :list_jobs_hosts], :template_invocations => [:show, :show_template_invocation_by_host],
permission :view_job_invocations, { :job_invocations => [:index, :chart, :show, :auto_complete_search, :preview_job_invocations_per_host], :template_invocations => [:show, :show_template_invocation_by_host],
'api/v2/job_invocations' => [:index, :show, :output, :raw_output, :outputs, :hosts] }, :resource_type => 'JobInvocation'
permission :view_template_invocations, { :template_invocations => [:show, :template_invocation_preview, :show_template_invocation_by_host], :job_invocations => [:list_jobs_hosts],
permission :view_template_invocations, { :template_invocations => [:show, :template_invocation_preview, :show_template_invocation_by_host],
'api/v2/template_invocations' => [:template_invocations], :ui_job_wizard => [:job_invocation] }, :resource_type => 'TemplateInvocation'
permission :create_template_invocations, {}, :resource_type => 'TemplateInvocation'
permission :execute_jobs_on_infrastructure_hosts, {}, :resource_type => 'JobInvocation'
Expand Down
53 changes: 46 additions & 7 deletions test/functional/api/v2/job_invocations_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -284,20 +284,59 @@ class JobInvocationsControllerTest < ActionController::TestCase
end

describe '#hosts' do
test 'should compute job_status for the paginated subset' do
invocation = FactoryBot.create(:job_invocation, :with_template, :with_task)
2.times { invocation.template_invocations << FactoryBot.create(:template_invocation, :with_task, :with_host, :job_invocation => invocation) }
invocation.job_category = invocation.pattern_template_invocations.first.template.job_category
invocation.targeting.hosts = invocation.template_invocations.map(&:host)
invocation.save!
setup do
@hosts_invocation = FactoryBot.create(:job_invocation, :with_template, :with_task)
2.times { @hosts_invocation.template_invocations << FactoryBot.create(:template_invocation, :with_task, :with_host, :job_invocation => @hosts_invocation) }
@hosts_invocation.job_category = @hosts_invocation.pattern_template_invocations.first.template.job_category
@hosts_invocation.targeting.hosts = @hosts_invocation.template_invocations.map(&:host)
@hosts_invocation.save!
end

get :hosts, params: { :id => invocation.id, :page => 2, :per_page => 1 }
test 'should compute job_status for the paginated subset' do
get :hosts, params: { :id => @hosts_invocation.id, :page => 2, :per_page => 1 }
assert_response :success
result = ActiveSupport::JSON.decode(@response.body)
assert_equal 3, result['total']
assert_equal 1, result['results'].size
assert_equal(['success'], result['results'].map { |r| r['job_status'] })
end

test 'should not include task and permissions without include_permissions' do
get :hosts, params: { :id => @hosts_invocation.id }
assert_response :success
result = ActiveSupport::JSON.decode(@response.body)
result['results'].each do |host|
assert_not host.key?('task'), 'Expected task to be absent'
assert_not host.key?('permissions'), 'Expected permissions to be absent'
end
end

test 'should include task and permissions with include_permissions=true' do
get :hosts, params: { :id => @hosts_invocation.id, :include_permissions => true }
assert_response :success
result = ActiveSupport::JSON.decode(@response.body)
result['results'].each do |host|
assert host.key?('task'), 'Expected task to be present'
assert host.key?('permissions'), 'Expected permissions to be present'
permissions = host['permissions']
assert permissions.key?('view_foreman_tasks')
assert permissions.key?('cancel_job_invocations')
assert permissions.key?('execute_jobs')
end
end

test 'should handle hosts with nil task gracefully when include_permissions=true' do
invocation = FactoryBot.create(:job_invocation, :with_template, :with_task, :with_unplanned_host)
invocation.job_category = invocation.pattern_template_invocations.first.template.job_category
invocation.save!

get :hosts, params: { :id => invocation.id, :include_permissions => true }
assert_response :success
result = ActiveSupport::JSON.decode(@response.body)
result['results'].each do |host|
assert host.key?('permissions'), 'Expected permissions to be present'
end
end
end

describe 'raw output' do
Expand Down
Loading
Loading