foreman_remote_execution 0.0.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 +15 -0
- data/LICENSE +619 -0
- data/README.md +54 -0
- data/Rakefile +47 -0
- data/app/assets/javascripts/template_input.js +9 -0
- data/app/assets/javascripts/template_invocation.js +32 -0
- data/app/assets/stylesheets/job_invocations.css.scss +35 -0
- data/app/assets/stylesheets/template_invocation.css.scss +16 -0
- data/app/controllers/api/v2/job_templates_controller.rb +108 -0
- data/app/controllers/job_invocations_controller.rb +35 -0
- data/app/controllers/job_templates_controller.rb +35 -0
- data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +40 -0
- data/app/helpers/remote_execution_helper.rb +88 -0
- data/app/lib/actions/remote_execution/run_host_job.rb +93 -0
- data/app/lib/actions/remote_execution/run_hosts_job.rb +35 -0
- data/app/lib/actions/remote_execution/run_proxy_command.rb +34 -0
- data/app/models/concerns/foreman_remote_execution/bookmark_extensions.rb +9 -0
- data/app/models/concerns/foreman_remote_execution/foreman_tasks_task_extensions.rb +9 -0
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +19 -0
- data/app/models/concerns/foreman_remote_execution/template_extensions.rb +20 -0
- data/app/models/concerns/foreman_remote_execution/template_relations.rb +10 -0
- data/app/models/concerns/foreman_remote_execution/user_extensions.rb +9 -0
- data/app/models/input_template_renderer.rb +42 -0
- data/app/models/job_invocation.rb +21 -0
- data/app/models/job_invocation_composer.rb +210 -0
- data/app/models/job_template.rb +52 -0
- data/app/models/remote_execution_provider.rb +17 -0
- data/app/models/setting/remote_execution.rb +19 -0
- data/app/models/ssh_execution_provider.rb +2 -0
- data/app/models/targeting.rb +56 -0
- data/app/models/targeting_host.rb +9 -0
- data/app/models/template_input.rb +154 -0
- data/app/models/template_invocation.rb +13 -0
- data/app/models/template_invocation_input_value.rb +8 -0
- data/app/views/api/v2/job_templates/base.json.rabl +3 -0
- data/app/views/api/v2/job_templates/create.json.rabl +3 -0
- data/app/views/api/v2/job_templates/index.json.rabl +3 -0
- data/app/views/api/v2/job_templates/main.json.rabl +5 -0
- data/app/views/api/v2/job_templates/show.json.rabl +9 -0
- data/app/views/job_invocations/_form.html.erb +67 -0
- data/app/views/job_invocations/_tab_hosts.html.erb +33 -0
- data/app/views/job_invocations/_tab_overview.html.erb +41 -0
- data/app/views/job_invocations/index.html.erb +30 -0
- data/app/views/job_invocations/new.html.erb +8 -0
- data/app/views/job_invocations/refresh.js.erb +1 -0
- data/app/views/job_invocations/show.html.erb +21 -0
- data/app/views/job_templates/_custom_tab_headers.html.erb +2 -0
- data/app/views/job_templates/_custom_tabs.html.erb +28 -0
- data/app/views/job_templates/auto_complete_job_name.json.erb +3 -0
- data/app/views/job_templates/edit.html.erb +6 -0
- data/app/views/job_templates/index.html.erb +33 -0
- data/app/views/job_templates/new.html.erb +6 -0
- data/app/views/template_inputs/_form.html.erb +22 -0
- data/config/routes.rb +35 -0
- data/db/migrate/20150612121541_add_job_template_to_template.rb +6 -0
- data/db/migrate/20150616080015_create_template_input.rb +19 -0
- data/db/migrate/20150708133241_add_targeting.rb +25 -0
- data/db/migrate/20150708133242_add_invocation.rb +11 -0
- data/db/migrate/20150708133305_add_template_invocation.rb +22 -0
- data/db/migrate/20150812110800_add_resolved_at_to_targeting.rb +5 -0
- data/db/migrate/20150812145900_add_last_task_id_to_job_invocation.rb +6 -0
- data/db/seeds.d/60-ssh_proxy_feature.rb +2 -0
- data/lib/foreman_remote_execution/engine.rb +119 -0
- data/lib/foreman_remote_execution/version.rb +3 -0
- data/lib/foreman_remote_execution.rb +6 -0
- data/lib/tasks/foreman_remote_execution_tasks.rake +49 -0
- data/locale/Makefile +62 -0
- data/locale/en/foreman_remote_execution.po +19 -0
- data/locale/foreman_remote_execution.pot +19 -0
- data/locale/gemspec.rb +2 -0
- data/test/factories/foreman_remote_execution_factories.rb +48 -0
- data/test/functional/api/v2/job_templates_controller_test.rb +74 -0
- data/test/test_plugin_helper.rb +8 -0
- data/test/unit/actions/run_hosts_job_test.rb +40 -0
- data/test/unit/actions/run_proxy_command_test.rb +30 -0
- data/test/unit/input_template_renderer_test.rb +366 -0
- data/test/unit/job_invocation_composer_test.rb +415 -0
- data/test/unit/job_invocation_test.rb +31 -0
- data/test/unit/job_template_test.rb +5 -0
- data/test/unit/remote_execution_provider_test.rb +51 -0
- data/test/unit/targeting_test.rb +107 -0
- data/test/unit/template_input_test.rb +25 -0
- metadata +195 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<%= form_for @composer.job_invocation, :html => { 'data-refresh-url' => refresh_job_invocations_path, :id => 'job_invocation_form' } do |f| %>
|
|
2
|
+
<%= selectable_f f, :job_name, @composer.available_job_names %>
|
|
3
|
+
|
|
4
|
+
<%= fields_for @composer.targeting do |targeting_fields| %>
|
|
5
|
+
<%= targeting_fields.hidden_field :bookmark_id, :value => params[:bookmark_id] %>
|
|
6
|
+
<%= textarea_f targeting_fields, :search_query, :value => @composer.displayed_search_query, :rows => 5 %>
|
|
7
|
+
|
|
8
|
+
<div class="form-group ">
|
|
9
|
+
<label class="col-md-2 control-label"><%= _('Resolves to') %></label>
|
|
10
|
+
<div class="col-md-4">
|
|
11
|
+
<%= @composer.targeted_hosts_count %> <%= _('hosts') %>
|
|
12
|
+
<%= button_tag(:type => 'button', :class => 'btn btn-default btn-sm', :title => _("Refresh"), :id => 'refresh_execution_form') do %>
|
|
13
|
+
<%= icon_text('refresh') %>
|
|
14
|
+
<% end %>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div class="form-group ">
|
|
19
|
+
<label class="col-md-2 control-label"><%= _('Type of query') %></label>
|
|
20
|
+
<div class="col-md-4">
|
|
21
|
+
<%= radio_button_f targeting_fields, :targeting_type, :value => Targeting::STATIC_TYPE, :text => _(Targeting::TYPES[Targeting::STATIC_TYPE]) %>
|
|
22
|
+
<%= radio_button_f targeting_fields, :targeting_type, :value => Targeting::DYNAMIC_TYPE, :text => _(Targeting::TYPES[Targeting::DYNAMIC_TYPE]) %>
|
|
23
|
+
</div>
|
|
24
|
+
<span class="help-inline"><%= popover(_('Explanation'),
|
|
25
|
+
_("Type has impact on when is the query evaulated to hosts.<br><ul><li><b>Static</b> - evaluates just after you submit this form</li><li><b>Dynamic</b> - evaluates just before the execution is started, so if it's planed in future, targeted hosts set may change before it</li></ul>")) %></span>
|
|
26
|
+
</div>
|
|
27
|
+
<% end %>
|
|
28
|
+
|
|
29
|
+
<% @composer.displayed_provider_types.each do |provider_type| %>
|
|
30
|
+
<fieldset id="provider_<%= provider_type %>" class="provider_form">
|
|
31
|
+
<legend><%= _('Provider') + ': ' + provider_type %></legend>
|
|
32
|
+
<%= f.fields_for 'providers' do |providers_fields| %>
|
|
33
|
+
<%= providers_fields.fields_for provider_type do |provider_type_fields| %>
|
|
34
|
+
<%= provider_type_fields.fields_for :job_templates do |job_templates_fields| %>
|
|
35
|
+
<% if @composer.needs_provider_type_selection? %>
|
|
36
|
+
<%= radio_button_f provider_type_fields, 'job_template_id', :value => '', :text => _('Disable this provider'),
|
|
37
|
+
:class => 'job_template_selector', :checked => @composer.preselect_disabled_for_provider(provider_type) %>
|
|
38
|
+
<span />
|
|
39
|
+
<% end %>
|
|
40
|
+
<% @composer.templates_for_provider(provider_type).each do |job_template| %>
|
|
41
|
+
<%= radio_button_f provider_type_fields, 'job_template_id',
|
|
42
|
+
:value => job_template.id,
|
|
43
|
+
:text => job_template.name,
|
|
44
|
+
:class => 'job_template_selector',
|
|
45
|
+
:checked => @composer.job_template_ids.include?(job_template.id) || @composer.only_one_template_available? %>
|
|
46
|
+
|
|
47
|
+
<fieldset id="job_template_<%= job_template.id %>" class="job_template_form <%= 'hidden' if !@composer.job_template_ids.include?(job_template.id) && !@composer.only_one_template_available? %>">
|
|
48
|
+
<%= job_templates_fields.fields_for job_template.id.to_s do |job_template_fields| %>
|
|
49
|
+
<%= job_template_fields.fields_for :input_values do |input_values_fields| %>
|
|
50
|
+
<% job_template.template_inputs.where(:input_type => 'user').each do |input| %>
|
|
51
|
+
<%= input_values_fields.fields_for input.id.to_s, @composer.template_invocation_input_value_for(input) do |input_fields| %>
|
|
52
|
+
<%= text_f input_fields, :value, :label => input.name, :help_inline => input.description, :required => input.required %>
|
|
53
|
+
<% end %>
|
|
54
|
+
<% end %>
|
|
55
|
+
<% end %>
|
|
56
|
+
<% end %>
|
|
57
|
+
</fieldset>
|
|
58
|
+
<% end %>
|
|
59
|
+
<% end %>
|
|
60
|
+
<% end %>
|
|
61
|
+
<% end %>
|
|
62
|
+
</fieldset>
|
|
63
|
+
<% end %>
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
<%= submit_or_cancel f %>
|
|
67
|
+
<% end %>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<% if job_invocation.last_task.present? %>
|
|
2
|
+
<table class="table table-bordered table-striped table-condensed">
|
|
3
|
+
<thead>
|
|
4
|
+
<tr>
|
|
5
|
+
<th><%= _('Host') %></th>
|
|
6
|
+
<th><%= _('Provider') %></th>
|
|
7
|
+
<th><%= _('Status') %></th>
|
|
8
|
+
<th><%= _('Actions') %></th>
|
|
9
|
+
</tr>
|
|
10
|
+
</thead>
|
|
11
|
+
|
|
12
|
+
<tbody>
|
|
13
|
+
<% job_invocation.last_task.sub_tasks.for_action_types('Actions::RemoteExecution::RunHostJob').each do |task| %>
|
|
14
|
+
<tr>
|
|
15
|
+
<% template_invocation = task.locks.where(:resource_type => 'TemplateInvocation').first.try(:resource) %>
|
|
16
|
+
<% if (host = task.locks.where(:resource_type => 'Host::Managed').first.try(:resource)) %>
|
|
17
|
+
<td><%= link_to_if_authorized host.name, hash_for_host_path(host).merge(:auth_object => host, :permission => :view_hosts) %></td>
|
|
18
|
+
<% else %>
|
|
19
|
+
<td><%= _('host not linked') %></td>
|
|
20
|
+
<% end %>
|
|
21
|
+
|
|
22
|
+
<td><%= template_invocation.nil? ? _('N/A') : _(RemoteExecutionProvider.provider_for(template_invocation.template.provider_type)) %></td>
|
|
23
|
+
<td><%= template_invocation_status(task) %></td>
|
|
24
|
+
<td><%= action_buttons(
|
|
25
|
+
display_link_if_authorized(_("Details"), hash_for_foreman_tasks_task_path(:id => task).merge(:auth_object => host, :permission => :view_foreman_tasks))
|
|
26
|
+
) %></td>
|
|
27
|
+
</tr>
|
|
28
|
+
<% end %>
|
|
29
|
+
</tbody>
|
|
30
|
+
</table>
|
|
31
|
+
<% else %>
|
|
32
|
+
<%= _('No host could be found, this can happen if the action was not executed yet. Try visiting this page later.') %>
|
|
33
|
+
<% end %>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<div id="status_chart">
|
|
2
|
+
<%= job_invocation_chart(job_invocation) %>
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
<div class="col-md-6 infoblock">
|
|
6
|
+
<h4><%= _('Target hosts') %></h4>
|
|
7
|
+
<% if job_invocation.bookmark.present? %>
|
|
8
|
+
<%= _('Bookmark') %> <%= job_invocation.bookmark.name %><br>
|
|
9
|
+
<% else %>
|
|
10
|
+
<%= _('Manual selection') %>
|
|
11
|
+
<% end %>
|
|
12
|
+
<%= 'using ' + _(Targeting::TYPES[job_invocation.targeting.targeting_type]) %>
|
|
13
|
+
<pre><%= job_invocation.targeting.search_query %></pre>
|
|
14
|
+
|
|
15
|
+
<%= _('Evaluated at:') %> <%= job_invocation.targeting.resolved_at %><br>
|
|
16
|
+
<% if job_invocation.template_invocations.size > 1 %>
|
|
17
|
+
<% job_invocation.template_invocations.each do |template_invocation| %>
|
|
18
|
+
<%= host_counter _(RemoteExecutionProvider.provider_for(template_invocation.template.provider_type)), ForemanTasks::Task::DynflowTask.for_action(Actions::RemoteExecution::RunHostJob).for_resource(template_invocation).uniq.size %>
|
|
19
|
+
<% end %>
|
|
20
|
+
<% end %>
|
|
21
|
+
<%= host_counter(_('Total hosts'), job_invocation.last_task.output['total_count']) if job_invocation.last_task %>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div class="col-md-6 infoblock">
|
|
25
|
+
<h4><%= _('Providers and templates') %></h4>
|
|
26
|
+
<% job_invocation.template_invocations.each do |template_invocation| %>
|
|
27
|
+
<h5>
|
|
28
|
+
<b><%= template_invocation.template.name %></b> <%= 'through' %> <%= _(RemoteExecutionProvider.provider_for(template_invocation.template.provider_type)) %>
|
|
29
|
+
</h5>
|
|
30
|
+
<pre><%= InputTemplateRenderer.new(template_invocation.template, nil, template_invocation).preview %></pre>
|
|
31
|
+
|
|
32
|
+
<% if template_invocation.input_values.present? %>
|
|
33
|
+
<%= _('following user inputs were provided') %>
|
|
34
|
+
<ul>
|
|
35
|
+
<% template_invocation.input_values.joins(:template_input).each do |input_value| %>
|
|
36
|
+
<li><b><%= input_value.template_input.name %></b>: <%= input_value.value %></li>
|
|
37
|
+
<% end %>
|
|
38
|
+
</ul>
|
|
39
|
+
<% end %>
|
|
40
|
+
<% end %>
|
|
41
|
+
</div>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<% title _('Job invocations') %>
|
|
2
|
+
|
|
3
|
+
<table class="table table-bordered table-striped table-condensed">
|
|
4
|
+
<thead>
|
|
5
|
+
<tr>
|
|
6
|
+
<th><%= _('Job name') %></th>
|
|
7
|
+
<th><%= _('Status') %></th>
|
|
8
|
+
<th><%= _('Succeeded') %></th>
|
|
9
|
+
<th><%= _('Failed') %></th>
|
|
10
|
+
<th><%= _('Pending') %></th>
|
|
11
|
+
<th><%= _('Total hosts') %></th>
|
|
12
|
+
</tr>
|
|
13
|
+
</thead>
|
|
14
|
+
|
|
15
|
+
<tbody>
|
|
16
|
+
<% @job_invocations.each do |invocation| %>
|
|
17
|
+
<tr>
|
|
18
|
+
<td><%= link_to_if_authorized invocation.job_name, hash_for_job_invocation_path(invocation).merge(:auth_object => invocation, :permission => :view_job_invocation) %></td>
|
|
19
|
+
<td><%= link_to_invocation_task_if_authorized(invocation) %></td>
|
|
20
|
+
<td><%= invocation_count(invocation, :output_key => :success_count) %></td>
|
|
21
|
+
<td><%= invocation_count(invocation, :output_key => :failed_count) %></td>
|
|
22
|
+
<td><%= invocation_count(invocation, :output_key => :pending_count) %></td>
|
|
23
|
+
<td><%= invocation_count(invocation, :output_key => :total_count) %></td>
|
|
24
|
+
</tr>
|
|
25
|
+
<% end %>
|
|
26
|
+
</tbody>
|
|
27
|
+
|
|
28
|
+
</table>
|
|
29
|
+
|
|
30
|
+
<%= will_paginate_with_info @job_invocations %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
$('form#job_invocation_form').html("<%=j render :partial => 'form' %>");
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<% title @job_invocation.job_name %>
|
|
2
|
+
<% stylesheet 'job_invocations' %>
|
|
3
|
+
|
|
4
|
+
<% if @job_invocation.last_task %>
|
|
5
|
+
<% title_actions(job_invocation_task_buttons(@job_invocation.last_task)) %>
|
|
6
|
+
<% end %>
|
|
7
|
+
|
|
8
|
+
<ul class="nav nav-tabs" data-tabs="tabs">
|
|
9
|
+
<li class="active"><a href="#primary" data-toggle="tab"><%= _('Overview') %></a></li>
|
|
10
|
+
<li><a href="#hosts" data-toggle="tab"><%= _('Hosts') %></a></li>
|
|
11
|
+
</ul>
|
|
12
|
+
|
|
13
|
+
<div class="tab-content">
|
|
14
|
+
<div class="tab-pane active" id="primary">
|
|
15
|
+
<%= render 'tab_overview', :job_invocation => @job_invocation %>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div class="tab-pane" id="hosts">
|
|
19
|
+
<%= render 'tab_hosts', :job_invocation => @job_invocation %>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<div class="tab-pane" id="template_job">
|
|
2
|
+
|
|
3
|
+
<%= field(f, :job_name) do %>
|
|
4
|
+
<%= auto_complete_search(:job_name,
|
|
5
|
+
f.object.job_name,
|
|
6
|
+
:placeholder => _("Job name") + ' ...',
|
|
7
|
+
:name => 'job_template[job_name]',
|
|
8
|
+
:id => 'search') %>
|
|
9
|
+
<% end %>
|
|
10
|
+
|
|
11
|
+
<%= select_f f, :provider_type, providers_options, :first, :last %>
|
|
12
|
+
|
|
13
|
+
<div class="children_fields">
|
|
14
|
+
<%= new_child_fields_template(f, :template_inputs, { :partial => "template_inputs/form" }) %>
|
|
15
|
+
<%= f.fields_for :template_inputs do |ff| %>
|
|
16
|
+
<div class="template_input_form fields">
|
|
17
|
+
<%= render 'template_inputs/form', :f => ff %>
|
|
18
|
+
</div>
|
|
19
|
+
<% end %>
|
|
20
|
+
<%= add_child_link '+ ' + _("Add Input"), :template_inputs, { :title => _('add a input for this template')} %>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
<div class="tab-pane" id="template_type">
|
|
27
|
+
<%= checkbox_f f, :snippet, :onchange => "snippet_changed(this)", :label=>_('Snippet'), :disabled => @template.locked? %>
|
|
28
|
+
</div>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<%= include_javascript %>
|
|
2
|
+
<%= javascript 'lookup_keys' %>
|
|
3
|
+
<%= javascript 'template_input' %>
|
|
4
|
+
|
|
5
|
+
<% title _("Job Templates") %>
|
|
6
|
+
<% title_actions display_link_if_authorized(_("New Job Template"), hash_for_new_job_template_path) %>
|
|
7
|
+
|
|
8
|
+
<table class="table table-bordered table-striped table-two-pane">
|
|
9
|
+
<thead>
|
|
10
|
+
<tr>
|
|
11
|
+
<th><%= sort :name, :as => s_("JobTemplate|Name") %></th>
|
|
12
|
+
<th><%= sort :snippet, :as => s_("JobTemplate|Snippet") %></th>
|
|
13
|
+
<th><%= sort :locked, :as => s_("JobTemplate|Locked"), :default => "DESC" %></th>
|
|
14
|
+
<th></th>
|
|
15
|
+
</tr>
|
|
16
|
+
</thead>
|
|
17
|
+
<tbody>
|
|
18
|
+
<% for job_template in @templates %>
|
|
19
|
+
<tr>
|
|
20
|
+
<td class="display-two-pane"><%= link_to_if_authorized trunc_with_tooltip(job_template),
|
|
21
|
+
hash_for_edit_job_template_path(:id => job_template).merge(:auth_object => job_template, :authorizer => authorizer) %></td>
|
|
22
|
+
<td align='center'><%= checked_icon job_template.snippet %></td>
|
|
23
|
+
<td align='center'>
|
|
24
|
+
<%= locked_icon job_template.locked?, _("This template is locked for editing.") %>
|
|
25
|
+
</td>
|
|
26
|
+
<td>
|
|
27
|
+
<%= action_buttons(*permitted_actions(job_template)) %>
|
|
28
|
+
</td>
|
|
29
|
+
</tr>
|
|
30
|
+
<% end %>
|
|
31
|
+
</tbody>
|
|
32
|
+
</table>
|
|
33
|
+
<%= will_paginate_with_info @templates %>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<div class="fields">
|
|
2
|
+
<div class="form-group">
|
|
3
|
+
<%= field_set_tag _("Template input").html_safe +
|
|
4
|
+
' ' +
|
|
5
|
+
remove_child_link('x', f, {:rel => 'twipsy', "data-title" => _('remove template input'), :'data-placement' => 'left', :class => 'fr badge badge-danger'}).html_safe do %>
|
|
6
|
+
<%= text_f f, :name %>
|
|
7
|
+
<%= checkbox_f f, :required %>
|
|
8
|
+
<%= selectable_f f, :input_type, template_input_types_options, {}, :class => 'input_type_selector' %>
|
|
9
|
+
<div class="fact_input_type custom_input_type_fields" style="<%= f.object.fact_template_input? ? '' : 'display:none' %>">
|
|
10
|
+
<%= text_f f, :fact_name, :class => 'fact_input_type', :required => true %>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="variable_input_type custom_input_type_fields" style="<%= f.object.variable_template_input? ? '' : 'display:none' %>">
|
|
13
|
+
<%= text_f f, :variable_name, :class => 'variable_input_type', :required => true %>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="puppet_parameter_input_type custom_input_type_fields" style="<%= f.object.puppet_parameter_template_input? ? '' : 'display:none' %>">
|
|
16
|
+
<%= text_f f, :puppet_class_name, :class => 'puppet_parameter_input_type', :required => true %>
|
|
17
|
+
<%= text_f f, :puppet_parameter_name, :class => 'puppet_parameter_input_type', :required => true %>
|
|
18
|
+
</div>
|
|
19
|
+
<%= textarea_f f, :description, :rows => 3 %>
|
|
20
|
+
<% end %>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Rails.application.routes.draw do
|
|
2
|
+
resources :job_templates, :except => [:show] do
|
|
3
|
+
member do
|
|
4
|
+
get 'clone_template'
|
|
5
|
+
get 'lock'
|
|
6
|
+
get 'unlock'
|
|
7
|
+
post 'preview'
|
|
8
|
+
end
|
|
9
|
+
collection do
|
|
10
|
+
get 'revision'
|
|
11
|
+
get 'auto_complete_search'
|
|
12
|
+
get 'auto_complete_job_name'
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
resources :job_invocations, :only => [:new, :create, :show, :index] do
|
|
17
|
+
collection do
|
|
18
|
+
post 'refresh'
|
|
19
|
+
get 'auto_complete_search'
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
namespace :api, :defaults => {:format => 'json'} do
|
|
24
|
+
scope "(:apiv)", :module => :v2, :defaults => {:apiv => 'v2'}, :apiv => /v1|v2/, :constraints => ApiConstraints.new(:version => 2, :default => true) do
|
|
25
|
+
resources :job_templates, :except => [:new, :edit] do
|
|
26
|
+
(resources :locations, :only => [:index, :show]) if SETTINGS[:locations_enabled]
|
|
27
|
+
(resources :organizations, :only => [:index, :show]) if SETTINGS[:organizations_enabled]
|
|
28
|
+
post :clone, :on => :member
|
|
29
|
+
collection do
|
|
30
|
+
get 'revision'
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class CreateTemplateInput < ActiveRecord::Migration
|
|
2
|
+
def change
|
|
3
|
+
create_table :template_inputs do |t|
|
|
4
|
+
t.string :name, :null => false
|
|
5
|
+
t.boolean :required, :null => false, :default => false
|
|
6
|
+
t.string :input_type, :null => false
|
|
7
|
+
t.string :fact_name
|
|
8
|
+
t.string :variable_name
|
|
9
|
+
t.string :puppet_class_name
|
|
10
|
+
t.string :puppet_parameter_name
|
|
11
|
+
t.text :description
|
|
12
|
+
t.integer :template_id
|
|
13
|
+
|
|
14
|
+
t.timestamps
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
add_foreign_key :template_inputs, :templates, :name => "templates_template_id_fk", :column => 'template_id'
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class AddTargeting < ActiveRecord::Migration
|
|
2
|
+
def change
|
|
3
|
+
create_table :targetings do |t|
|
|
4
|
+
t.string :search_query
|
|
5
|
+
t.references :bookmark
|
|
6
|
+
t.references :user
|
|
7
|
+
t.string :targeting_type, :null => false
|
|
8
|
+
t.timestamps
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
add_index :targetings, [:bookmark_id], :name => "targetings_bookmark_id"
|
|
12
|
+
add_index :targetings, [:user_id], :name => "targetings_user_id"
|
|
13
|
+
add_foreign_key :targetings, :bookmarks, :name => "targetings_bookmark_id", :column => 'bookmark_id'
|
|
14
|
+
add_foreign_key :targetings, :users, :name => "targetings_user_id", :column => 'user_id'
|
|
15
|
+
|
|
16
|
+
create_table :targeting_hosts do |t|
|
|
17
|
+
t.references :host, :null => false
|
|
18
|
+
t.references :targeting, :null => false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
add_index :targeting_hosts, [:host_id, :targeting_id], :name => "targeting_hosts_host_targeting_ids"
|
|
22
|
+
add_foreign_key :targeting_hosts, :hosts, :name => "targeting_hosts_host_id", :column => 'host_id'
|
|
23
|
+
add_foreign_key :targeting_hosts, :targetings, :name => "targeting_hosts_targeting_id", :column => 'targeting_id'
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class AddInvocation< ActiveRecord::Migration
|
|
2
|
+
def change
|
|
3
|
+
create_table :job_invocations do |t|
|
|
4
|
+
t.references :targeting, :null => false
|
|
5
|
+
t.string :job_name, :null => false
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
add_index :job_invocations, [:targeting_id], :name => "job_invocations_targeting_id"
|
|
9
|
+
add_foreign_key :job_invocations, :targetings, :name => "job_invocation_targeting_id", :column => 'targeting_id'
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class AddTemplateInvocation < ActiveRecord::Migration
|
|
2
|
+
def change
|
|
3
|
+
create_table :template_invocations do |t|
|
|
4
|
+
t.references :template, :null => false
|
|
5
|
+
t.references :job_invocation, :null => false
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
add_index :template_invocations, [:template_id, :job_invocation_id], :name => "targeting_invocation_template_ji_ids"
|
|
9
|
+
add_foreign_key :template_invocations, :templates, :name => "template_invoc_template_id", :column => 'template_id'
|
|
10
|
+
add_foreign_key :template_invocations, :job_invocations, :name => "template_invoc_job_invocation_id", :column => 'job_invocation_id'
|
|
11
|
+
|
|
12
|
+
create_table :template_invocation_input_values do |t|
|
|
13
|
+
t.references :template_invocation, :null => false
|
|
14
|
+
t.references :template_input, :null => false
|
|
15
|
+
t.string :value, :null => false
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
add_index :template_invocation_input_values, [:template_invocation_id, :template_input_id], :name => "template_invocation_input_values_ti_ti_ids"
|
|
19
|
+
add_foreign_key :template_invocation_input_values, :template_invocations, :name => "template_invoc_input_values_template_invoc_id", :column => 'template_invocation_id'
|
|
20
|
+
add_foreign_key :template_invocation_input_values, :template_inputs, :name => "template_invoc_input_values_template_input_id", :column => 'template_input_id'
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
module ForemanRemoteExecution
|
|
2
|
+
class Engine < ::Rails::Engine
|
|
3
|
+
engine_name 'foreman_remote_execution'
|
|
4
|
+
|
|
5
|
+
config.autoload_paths += Dir["#{config.root}/app/controllers/concerns"]
|
|
6
|
+
config.autoload_paths += Dir["#{config.root}/app/helpers/concerns"]
|
|
7
|
+
config.autoload_paths += Dir["#{config.root}/app/models/concerns"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
initializer 'foreman_remote_execution.load_default_settings', :before => :load_config_initializers do
|
|
11
|
+
require_dependency File.expand_path('../../../app/models/setting/remote_execution.rb', __FILE__) if (Setting.table_exists? rescue(false))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Add any db migrations
|
|
15
|
+
initializer 'foreman_remote_execution.load_app_instance_data' do |app|
|
|
16
|
+
app.config.paths['db/migrate'] += ForemanRemoteExecution::Engine.paths['db/migrate'].existent
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
initializer "foreman_remote_execution.apipie" do
|
|
20
|
+
Apipie.configuration.checksum_path += ['/api/']
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
initializer "foreman_remote_execution.require_dynflow", :before => "foreman_tasks.initialize_dynflow" do |app|
|
|
24
|
+
ForemanTasks.dynflow.require!
|
|
25
|
+
ForemanTasks.dynflow.config.eager_load_paths << File.join(ForemanRemoteExecution::Engine.root, 'app/lib/actions')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
initializer 'foreman_remote_execution.register_plugin', after: :finisher_hook do |_app|
|
|
29
|
+
Foreman::Plugin.register :foreman_remote_execution do
|
|
30
|
+
requires_foreman '>= 1.9'
|
|
31
|
+
|
|
32
|
+
apipie_documented_controllers ["#{ForemanRemoteExecution::Engine.root}/app/controllers/api/v2/*.rb"]
|
|
33
|
+
|
|
34
|
+
# Add permissions
|
|
35
|
+
security_block :foreman_remote_execution do
|
|
36
|
+
permission :view_job_templates, { :job_templates => [:index, :show, :revision, :auto_complete_search, :auto_complete_job_name, :preview],
|
|
37
|
+
:'api/v2/job_templates' => [:index, :show, :revision] }, :resource_type => 'JobTemplate'
|
|
38
|
+
permission :create_job_templates, { :job_templates => [:new, :create, :clone_template],
|
|
39
|
+
:'api/v2/job_templates' => [:create, :clone] }, :resource_type => 'JobTemplate'
|
|
40
|
+
permission :edit_job_templates, { :job_templates => [:edit, :update],
|
|
41
|
+
:'api/v2/job_templates' => [:update] }, :resource_type => 'JobTemplate'
|
|
42
|
+
permission :destroy_job_templates, { :job_templates => [:destroy],
|
|
43
|
+
:'api/v2/job_templates' => [:destroy] }, :resource_type => 'JobTemplate'
|
|
44
|
+
permission :lock_job_templates, { :job_templates => [:lock, :unlock] }, :resource_type => 'JobTemplate'
|
|
45
|
+
|
|
46
|
+
permission :view_job_invocations, { :job_invocations => [:index, :show, :auto_complete_search] }, :resource_type => 'JobInvocation'
|
|
47
|
+
|
|
48
|
+
permission :create_job_invocations, { :job_invocations => [:new, :create, :refresh] }, :resource_type => 'JobInvocation'
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Add a new role called 'ForemanRemoteExecution' if it doesn't exist
|
|
52
|
+
# role 'ForemanRemoteExecution', [:view_foreman_remote_execution]
|
|
53
|
+
|
|
54
|
+
# add menu entry
|
|
55
|
+
menu :top_menu, :job_templates,
|
|
56
|
+
url_hash: { controller: :job_templates, action: :index },
|
|
57
|
+
caption: N_('Job templates'),
|
|
58
|
+
parent: :hosts_menu,
|
|
59
|
+
after: :provisioning_templates
|
|
60
|
+
|
|
61
|
+
menu :top_menu, :job_invocations,
|
|
62
|
+
url_hash: { controller: :job_invocations, action: :index },
|
|
63
|
+
caption: N_('Jobs'),
|
|
64
|
+
parent: :monitor_menu,
|
|
65
|
+
after: :audits
|
|
66
|
+
|
|
67
|
+
# add dashboard widget
|
|
68
|
+
# widget 'foreman_remote_execution_widget', name: N_('Foreman plugin template widget'), sizex: 4, sizey: 1
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Precompile any JS or CSS files under app/assets/
|
|
73
|
+
# If requiring files from each other, list them explicitly here to avoid precompiling the same
|
|
74
|
+
# content twice.
|
|
75
|
+
assets_to_precompile =
|
|
76
|
+
Dir.chdir(root) do
|
|
77
|
+
Dir['app/assets/javascripts/**/*', 'app/assets/stylesheets/**/*'].map do |f|
|
|
78
|
+
f.split(File::SEPARATOR, 4).last
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
initializer 'foreman_remote_execution.assets.precompile' do |app|
|
|
82
|
+
app.config.assets.precompile += assets_to_precompile
|
|
83
|
+
end
|
|
84
|
+
initializer 'foreman_remote_execution.configure_assets', group: :assets do
|
|
85
|
+
SETTINGS[:foreman_remote_execution] = { assets: { precompile: assets_to_precompile } }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Include concerns in this config.to_prepare block
|
|
89
|
+
config.to_prepare do
|
|
90
|
+
# we have to define associations in Template and all descendants because
|
|
91
|
+
# reflect_on_association does not work when we add association after parent and child class
|
|
92
|
+
# are already loaded, causing issues when you try to destroy any child which iterates
|
|
93
|
+
# over all associations because of :dependant => :destroy
|
|
94
|
+
#
|
|
95
|
+
# e.g. having ProvisioningTemplate < Template, adding has_many :template_inputs to Template from concern
|
|
96
|
+
# Template.reflect_on_association :template_inputs # => <#Association...
|
|
97
|
+
# ProvisioningTemplate.reflect_on_association :template_inputs # => nil
|
|
98
|
+
require_dependency 'job_template'
|
|
99
|
+
(Template.descendants + [Template]).each { |klass| klass.send(:include, ForemanRemoteExecution::TemplateRelations) }
|
|
100
|
+
# similarly, attr_accessible :template_inputs_attributes does not work with STI
|
|
101
|
+
(Template.descendants + [Template]).each { |klass| klass.send(:include, ForemanRemoteExecution::TemplateExtensions) }
|
|
102
|
+
|
|
103
|
+
User.send(:include, ForemanRemoteExecution::UserExtensions)
|
|
104
|
+
(Host::Base.descendants + [Host::Base]).each do |klass|
|
|
105
|
+
klass.send(:include, ForemanRemoteExecution::HostExtensions)
|
|
106
|
+
klass.send(:include, ForemanTasks::Concerns::HostActionSubject)
|
|
107
|
+
end
|
|
108
|
+
Bookmark.send(:include, ForemanRemoteExecution::BookmarkExtensions)
|
|
109
|
+
HostsHelper.send(:include, ForemanRemoteExecution::HostsHelperExtensions)
|
|
110
|
+
ForemanTasks::Task.send(:include, ForemanRemoteExecution::ForemanTasksTaskExtensions)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
initializer 'foreman_remote_execution.register_gettext', after: :load_config_initializers do |_app|
|
|
114
|
+
locale_dir = File.join(File.expand_path('../../..', __FILE__), 'locale')
|
|
115
|
+
locale_domain = 'foreman_remote_execution'
|
|
116
|
+
Foreman::Gettext::Support.add_text_domain locale_domain, locale_dir
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|