foreman_remote_execution 1.4.5 → 1.4.6
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 +5 -5
 - data/.hound.yml +13 -0
 - data/.rubocop.yml +9 -0
 - data/app/controllers/api/v2/job_invocations_controller.rb +49 -6
 - data/app/controllers/job_invocations_controller.rb +29 -1
 - data/app/helpers/remote_execution_helper.rb +5 -5
 - data/app/lib/actions/remote_execution/run_host_job.rb +32 -20
 - data/app/lib/actions/remote_execution/run_hosts_job.rb +13 -8
 - data/app/models/foreign_input_set.rb +1 -1
 - data/app/models/job_invocation.rb +23 -1
 - data/app/models/job_invocation_composer.rb +25 -3
 - data/app/models/job_template.rb +2 -2
 - data/app/models/remote_execution_feature.rb +22 -10
 - data/app/models/setting/remote_execution.rb +34 -10
 - data/app/models/ssh_execution_provider.rb +8 -0
 - data/app/models/template_input.rb +1 -1
 - data/app/views/api/v2/job_templates/base.json.rabl +1 -1
 - data/app/views/job_invocations/_form.html.erb +11 -5
 - data/app/views/job_invocations/_tab_hosts.html.erb +1 -1
 - data/app/views/job_invocations/index.html.erb +1 -1
 - data/app/views/job_templates/index.html.erb +1 -1
 - data/app/views/remote_execution_features/index.html.erb +1 -1
 - data/app/views/template_inputs/_invocation_form.html.erb +2 -2
 - data/app/views/template_invocations/show.html.erb +1 -1
 - data/config/routes.rb +5 -0
 - data/db/migrate/20160113162007_expand_all_template_invocations.rb +1 -1
 - data/db/migrate/20171129103615_add_secrets_to_job_invocations.rb +6 -0
 - data/db/migrate/20180202072115_add_notification_builder_to_remote_execution_feature.rb +5 -0
 - data/db/migrate/20180202123215_add_feature_id_to_job_invocation.rb +6 -0
 - data/db/migrate/20180226095631_change_task_id_to_uuid.rb +31 -0
 - data/foreman_remote_execution.gemspec +1 -1
 - data/lib/foreman_remote_execution/engine.rb +3 -1
 - data/lib/foreman_remote_execution/version.rb +1 -1
 - data/test/factories/foreman_remote_execution_factories.rb +6 -0
 - data/test/functional/api/v2/job_invocations_controller_test.rb +139 -32
 - data/test/functional/job_invocations_controller_test.rb +49 -0
 - data/test/unit/actions/run_hosts_job_test.rb +10 -6
 - data/test/unit/concerns/foreman_tasks_cleaner_extensions_test.rb +1 -1
 - data/test/unit/job_invocation_composer_test.rb +43 -1
 - metadata +14 -10
 - data/app/models/concerns/foreman_remote_execution/exportable.rb +0 -71
 - data/test/unit/concerns/exportable_test.rb +0 -88
 
    
        data/app/models/job_template.rb
    CHANGED
    
    | 
         @@ -1,10 +1,10 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            class JobTemplate < ::Template
         
     | 
| 
       2 
     | 
    
         
            -
              include  
     | 
| 
      
 2 
     | 
    
         
            +
              include ::Exportable
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
       4 
4 
     | 
    
         
             
              class NonUniqueInputsError < Foreman::Exception
         
     | 
| 
       5 
5 
     | 
    
         
             
              end
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
       7 
     | 
    
         
            -
              attr_exportable : 
     | 
| 
      
 7 
     | 
    
         
            +
              attr_exportable :job_category, :description_format, :template_inputs,
         
     | 
| 
       8 
8 
     | 
    
         
             
                              :foreign_input_sets, :provider_type, :kind => ->(template) { template.class.name.underscore }
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
       10 
10 
     | 
    
         
             
              include Authorizable
         
     | 
| 
         @@ -1,9 +1,11 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            class RemoteExecutionFeature < ApplicationRecord
         
     | 
| 
       2 
     | 
    
         
            -
              VALID_OPTIONS = [:provided_inputs, :description, :host_action_button].freeze
         
     | 
| 
      
 2 
     | 
    
         
            +
              VALID_OPTIONS = [:provided_inputs, :description, :host_action_button, :notification_builder].freeze
         
     | 
| 
       3 
3 
     | 
    
         
             
              validates :label, :name, :presence => true, :uniqueness => true
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
              belongs_to :job_template
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
      
 7 
     | 
    
         
            +
              audited :only => :job_template_id
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
       7 
9 
     | 
    
         
             
              extend FriendlyId
         
     | 
| 
       8 
10 
     | 
    
         
             
              friendly_id :label
         
     | 
| 
       9 
11 
     | 
    
         | 
| 
         @@ -27,19 +29,29 @@ class RemoteExecutionFeature < ApplicationRecord 
     | 
|
| 
       27 
29 
     | 
    
         
             
                options[:host_action_button] = false unless options.key?(:host_action_button)
         
     | 
| 
       28 
30 
     | 
    
         | 
| 
       29 
31 
     | 
    
         
             
                feature = self.find_by(label: label)
         
     | 
| 
      
 32 
     | 
    
         
            +
                builder = options[:notification_builder] ? options[:notification_builder].to_s : nil
         
     | 
| 
       30 
33 
     | 
    
         | 
| 
       31 
     | 
    
         
            -
                attributes = { :name => name, 
     | 
| 
      
 34 
     | 
    
         
            +
                attributes = { :name => name,
         
     | 
| 
      
 35 
     | 
    
         
            +
                               :provided_input_names => options[:provided_inputs],
         
     | 
| 
      
 36 
     | 
    
         
            +
                               :description => options[:description],
         
     | 
| 
      
 37 
     | 
    
         
            +
                               :host_action_button => options[:host_action_button],
         
     | 
| 
      
 38 
     | 
    
         
            +
                               :notification_builder => builder }
         
     | 
| 
       32 
39 
     | 
    
         
             
                # in case DB does not have the attribute created yet but plugin initializer registers the feature, we need to skip this attribute
         
     | 
| 
       33 
     | 
    
         
            -
                 
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
      
 40 
     | 
    
         
            +
                attrs = [ :host_action_button, :notification_builder ]
         
     | 
| 
      
 41 
     | 
    
         
            +
                attrs.each do |attr|
         
     | 
| 
      
 42 
     | 
    
         
            +
                  unless self.attribute_names.include?(attr.to_s)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    attributes.delete(attr)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
       35 
45 
     | 
    
         
             
                end
         
     | 
| 
       36 
46 
     | 
    
         | 
| 
       37 
     | 
    
         
            -
                 
     | 
| 
       38 
     | 
    
         
            -
                  feature 
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
       40 
     | 
    
         
            -
                   
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
      
 47 
     | 
    
         
            +
                self.without_auditing do
         
     | 
| 
      
 48 
     | 
    
         
            +
                  if feature.nil?
         
     | 
| 
      
 49 
     | 
    
         
            +
                    feature = self.create!({ :label => label }.merge(attributes))
         
     | 
| 
      
 50 
     | 
    
         
            +
                  else
         
     | 
| 
      
 51 
     | 
    
         
            +
                    feature.attributes = attributes
         
     | 
| 
      
 52 
     | 
    
         
            +
                    feature.save if feature.changed?
         
     | 
| 
      
 53 
     | 
    
         
            +
                  end
         
     | 
| 
      
 54 
     | 
    
         
            +
                  return feature
         
     | 
| 
       42 
55 
     | 
    
         
             
                end
         
     | 
| 
       43 
     | 
    
         
            -
                return feature
         
     | 
| 
       44 
56 
     | 
    
         
             
              end
         
     | 
| 
       45 
57 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,5 +1,8 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            class Setting::RemoteExecution < Setting
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
              ::Setting::BLANK_ATTRS.concat %w{remote_execution_ssh_password remote_execution_ssh_key_passphrase}
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              # rubocop:disable Metrics/MethodLength
         
     | 
| 
       3 
6 
     | 
    
         
             
              def self.load_defaults
         
     | 
| 
       4 
7 
     | 
    
         
             
                # Check the table exists
         
     | 
| 
       5 
8 
     | 
    
         
             
                return unless super
         
     | 
| 
         @@ -9,42 +12,63 @@ class Setting::RemoteExecution < Setting 
     | 
|
| 
       9 
12 
     | 
    
         
             
                  [
         
     | 
| 
       10 
13 
     | 
    
         
             
                    self.set('remote_execution_fallback_proxy',
         
     | 
| 
       11 
14 
     | 
    
         
             
                             N_('Search the host for any proxy with Remote Execution, useful when the host has no subnet or the subnet does not have an execution proxy'),
         
     | 
| 
       12 
     | 
    
         
            -
                             false 
     | 
| 
      
 15 
     | 
    
         
            +
                             false,
         
     | 
| 
      
 16 
     | 
    
         
            +
                             N_('Fallback to Any Proxy')),
         
     | 
| 
       13 
17 
     | 
    
         
             
                    self.set('remote_execution_global_proxy',
         
     | 
| 
       14 
18 
     | 
    
         
             
                             N_('Search for remote execution proxy outside of the proxies assigned to the host. ' +
         
     | 
| 
       15 
19 
     | 
    
         
             
                             "If locations or organizations are enabled, the search will be limited to the host's " +
         
     | 
| 
       16 
20 
     | 
    
         
             
                             'organization or location.'),
         
     | 
| 
       17 
     | 
    
         
            -
                             true 
     | 
| 
      
 21 
     | 
    
         
            +
                             true,
         
     | 
| 
      
 22 
     | 
    
         
            +
                             N_('Enable Global Proxy')),
         
     | 
| 
       18 
23 
     | 
    
         
             
                    self.set('remote_execution_without_proxy',
         
     | 
| 
       19 
24 
     | 
    
         
             
                             N_('When enabled, the remote execution will try to run the commands directly, when no
         
     | 
| 
       20 
25 
     | 
    
         
             
                                 proxy with remote execution feature is configured for the host.'),
         
     | 
| 
       21 
     | 
    
         
            -
                             false 
     | 
| 
      
 26 
     | 
    
         
            +
                             false,
         
     | 
| 
      
 27 
     | 
    
         
            +
                             N_('Fallback Without Proxy')),
         
     | 
| 
       22 
28 
     | 
    
         
             
                    self.set('remote_execution_ssh_user',
         
     | 
| 
       23 
29 
     | 
    
         
             
                             N_('Default user to use for SSH.  You may override per host by setting a parameter called remote_execution_ssh_user.'),
         
     | 
| 
       24 
     | 
    
         
            -
                             'root' 
     | 
| 
      
 30 
     | 
    
         
            +
                             'root',
         
     | 
| 
      
 31 
     | 
    
         
            +
                             N_('SSH User')),
         
     | 
| 
       25 
32 
     | 
    
         
             
                    self.set('remote_execution_effective_user',
         
     | 
| 
       26 
33 
     | 
    
         
             
                             N_('Default user to use for executing the script. If the user differs from the SSH user, su or sudo is used to switch the user.'),
         
     | 
| 
       27 
     | 
    
         
            -
                             'root' 
     | 
| 
      
 34 
     | 
    
         
            +
                             'root',
         
     | 
| 
      
 35 
     | 
    
         
            +
                             N_('Effective User')),
         
     | 
| 
       28 
36 
     | 
    
         
             
                    self.set('remote_execution_effective_user_method',
         
     | 
| 
       29 
37 
     | 
    
         
             
                             N_('What command should be used to switch to the effective user. One of %s') % SSHExecutionProvider::EFFECTIVE_USER_METHODS.inspect,
         
     | 
| 
       30 
38 
     | 
    
         
             
                             'sudo',
         
     | 
| 
       31 
     | 
    
         
            -
                             ' 
     | 
| 
      
 39 
     | 
    
         
            +
                             N_('Effective User Method'),
         
     | 
| 
       32 
40 
     | 
    
         
             
                             nil,
         
     | 
| 
       33 
41 
     | 
    
         
             
                             { :collection => proc { Hash[SSHExecutionProvider::EFFECTIVE_USER_METHODS.map { |method| [method, method] }] } }),
         
     | 
| 
       34 
42 
     | 
    
         
             
                    self.set('remote_execution_sync_templates',
         
     | 
| 
       35 
43 
     | 
    
         
             
                             N_('Whether we should sync templates from disk when running db:seed.'),
         
     | 
| 
       36 
     | 
    
         
            -
                             true 
     | 
| 
      
 44 
     | 
    
         
            +
                             true,
         
     | 
| 
      
 45 
     | 
    
         
            +
                             N_('Sync Job Templates')),
         
     | 
| 
       37 
46 
     | 
    
         
             
                    self.set('remote_execution_ssh_port',
         
     | 
| 
       38 
47 
     | 
    
         
             
                             N_('Port to use for SSH communication. Default port 22. You may override per host by setting a parameter called remote_execution_ssh_port.'),
         
     | 
| 
       39 
     | 
    
         
            -
                             '22' 
     | 
| 
      
 48 
     | 
    
         
            +
                             '22',
         
     | 
| 
      
 49 
     | 
    
         
            +
                             N_('SSH Port')),
         
     | 
| 
       40 
50 
     | 
    
         
             
                    self.set('remote_execution_connect_by_ip',
         
     | 
| 
       41 
51 
     | 
    
         
             
                             N_('Should the ip addresses on host interfaces be preferred over the fqdn? '\
         
     | 
| 
       42 
52 
     | 
    
         
             
                             'It is useful, when DNS not resolving the fqdns properly. You may override this per host by setting a parameter called remote_execution_connect_by_ip.'),
         
     | 
| 
       43 
     | 
    
         
            -
                             false 
     | 
| 
      
 53 
     | 
    
         
            +
                             false,
         
     | 
| 
      
 54 
     | 
    
         
            +
                             N_('Connect by IP')),
         
     | 
| 
      
 55 
     | 
    
         
            +
                    self.set('remote_execution_ssh_password',
         
     | 
| 
      
 56 
     | 
    
         
            +
                             N_('Default password to use for SSH. You may override per host by setting a parameter called remote_execution_ssh_password'),
         
     | 
| 
      
 57 
     | 
    
         
            +
                             nil,
         
     | 
| 
      
 58 
     | 
    
         
            +
                             N_('Default SSH password'),
         
     | 
| 
      
 59 
     | 
    
         
            +
                             nil,
         
     | 
| 
      
 60 
     | 
    
         
            +
                             { :encrypted => true }),
         
     | 
| 
      
 61 
     | 
    
         
            +
                    self.set('remote_execution_ssh_key_passphrase',
         
     | 
| 
      
 62 
     | 
    
         
            +
                             N_('Default key passphrase to use for SSH. You may override per host by setting a parameter called remote_execution_ssh_key_passphrase'),
         
     | 
| 
      
 63 
     | 
    
         
            +
                             nil,
         
     | 
| 
      
 64 
     | 
    
         
            +
                             N_('Default SSH key passphrase'),
         
     | 
| 
      
 65 
     | 
    
         
            +
                             nil,
         
     | 
| 
      
 66 
     | 
    
         
            +
                             { :encrypted => true })
         
     | 
| 
       44 
67 
     | 
    
         
             
                  ].each { |s| self.create! s.update(:category => 'Setting::RemoteExecution') }
         
     | 
| 
       45 
68 
     | 
    
         
             
                end
         
     | 
| 
       46 
69 
     | 
    
         | 
| 
       47 
70 
     | 
    
         
             
                true
         
     | 
| 
       48 
71 
     | 
    
         
             
              end
         
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
      
 72 
     | 
    
         
            +
              # rubocop:enable AbcSize
         
     | 
| 
      
 73 
     | 
    
         
            +
              # rubocop:enable Metrics/MethodLength
         
     | 
| 
       50 
74 
     | 
    
         
             
            end
         
     | 
| 
         @@ -15,6 +15,14 @@ class SSHExecutionProvider < RemoteExecutionProvider 
     | 
|
| 
       15 
15 
     | 
    
         
             
                  true
         
     | 
| 
       16 
16 
     | 
    
         
             
                end
         
     | 
| 
       17 
17 
     | 
    
         | 
| 
      
 18 
     | 
    
         
            +
                def ssh_password(host)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  host_setting(host, :remote_execution_ssh_password)
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                def ssh_key_passphrase(host)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  host_setting(host, :remote_execution_ssh_key_passphrase)
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
       18 
26 
     | 
    
         
             
                private
         
     | 
| 
       19 
27 
     | 
    
         | 
| 
       20 
28 
     | 
    
         
             
                def ssh_user(host)
         
     | 
| 
         @@ -7,6 +7,7 @@ 
     | 
|
| 
       7 
7 
     | 
    
         
             
            <%= form_for @composer.job_invocation, :html => {'data-refresh-url' => refresh_job_invocations_path, :id => 'job_invocation_form'} do |f| %>
         
     | 
| 
       8 
8 
     | 
    
         | 
| 
       9 
9 
     | 
    
         
             
              <%= selectable_f f, :job_category, @composer.available_job_categories, {}, :label => _('Job category') %>
         
     | 
| 
      
 10 
     | 
    
         
            +
              <%= f.hidden_field(:remote_execution_feature_id, :value => @composer.remote_execution_feature_id) %>
         
     | 
| 
       10 
11 
     | 
    
         | 
| 
       11 
12 
     | 
    
         
             
              <% selected_templates_per_provider = {} %>
         
     | 
| 
       12 
13 
     | 
    
         
             
              <% @composer.displayed_provider_types.each do |provider_type| %>
         
     | 
| 
         @@ -72,13 +73,13 @@ 
     | 
|
| 
       72 
73 
     | 
    
         | 
| 
       73 
74 
     | 
    
         
             
                                <div class="advanced hidden">
         
     | 
| 
       74 
75 
     | 
    
         
             
                                  <% if job_template.effective_user.overridable? %>
         
     | 
| 
       75 
     | 
    
         
            -
                                    <%= text_f job_template_fields, :effective_user, :value => @composer.template_invocation(job_template).try(:effective_user), :label => _('Effective user'), : 
     | 
| 
      
 76 
     | 
    
         
            +
                                    <%= text_f job_template_fields, :effective_user, :value => @composer.template_invocation(job_template).try(:effective_user), :label => _('Effective user'), :label_help => N_("A user to be used for executing the script. If it differs from the SSH user, su or sudo is used to switch the accounts.") %>
         
     | 
| 
       76 
77 
     | 
    
         
             
                                  <% end %>
         
     | 
| 
       77 
78 
     | 
    
         
             
                                  <%= render :partial => 'description_fields', :locals => { :f => f, :job_template => job_template, :disabled => job_template != selected_templates_per_provider[provider_type] } %>
         
     | 
| 
       78 
79 
     | 
    
         
             
                                </div>
         
     | 
| 
       79 
80 
     | 
    
         | 
| 
       80 
81 
     | 
    
         
             
                                <div class="advanced hidden">
         
     | 
| 
       81 
     | 
    
         
            -
                                  <%= number_f job_template_fields, :execution_timeout_interval, :value => f.object.execution_timeout_interval || job_template.execution_timeout_interval, :label => _('Timeout to kill'), : 
     | 
| 
      
 82 
     | 
    
         
            +
                                  <%= number_f job_template_fields, :execution_timeout_interval, :value => f.object.execution_timeout_interval || job_template.execution_timeout_interval, :label => _('Timeout to kill'), :label_help => N_('Time in seconds from the start on the remote host after which the job should be killed.') %>
         
     | 
| 
       82 
83 
     | 
    
         
             
                                </div>
         
     | 
| 
       83 
84 
     | 
    
         
             
                              <% end %>
         
     | 
| 
       84 
85 
     | 
    
         
             
                            </fieldset>
         
     | 
| 
         @@ -90,12 +91,17 @@ 
     | 
|
| 
       90 
91 
     | 
    
         
             
                <% end %>
         
     | 
| 
       91 
92 
     | 
    
         | 
| 
       92 
93 
     | 
    
         
             
                <div class="advanced hidden">
         
     | 
| 
       93 
     | 
    
         
            -
                  <%=  
     | 
| 
       94 
     | 
    
         
            -
                  <%=  
     | 
| 
      
 94 
     | 
    
         
            +
                  <%= password_f f, :password, :placeholder => '*****', :label => _('Password'), :label_help => N_('Password is stored encrypted in DB until the job finishes. For future or recurring executions, it is removed after the last execution.') %>
         
     | 
| 
      
 95 
     | 
    
         
            +
                  <%= password_f f, :key_passphrase, :placeholder => '*****', :label => _('Private key passphrase'), :label_help => N_('Key passhprase is only applicable for SSH provider. Other providers ignore this field. <br> Passphrase is stored encrypted in DB until the job finishes. For future or recurring executions, it is removed after the last execution.') %>
         
     | 
| 
      
 96 
     | 
    
         
            +
                </div>
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                <div class="advanced hidden">
         
     | 
| 
      
 99 
     | 
    
         
            +
                  <%= number_f f, :concurrency_level, :label => _('Concurrency level'), :placeholder => 'N', :min => 1, :label_help => N_("Run at most N tasks at a time") %>
         
     | 
| 
      
 100 
     | 
    
         
            +
                  <%= number_f f, :time_span, :label => _('Time span'), :placeholder => 'N', :min => 1, :label_help => N_("Distribute execution over N seconds")  %>
         
     | 
| 
       95 
101 
     | 
    
         
             
                </div>
         
     | 
| 
       96 
102 
     | 
    
         | 
| 
       97 
103 
     | 
    
         
             
                <div class="form-group advanced hidden">
         
     | 
| 
       98 
     | 
    
         
            -
                  <%= add_label({ :label => _('Type of query'), :label_help => _("Type has impact on when is the query  
     | 
| 
      
 104 
     | 
    
         
            +
                  <%= add_label({ :label => _('Type of query'), :label_help => _("Type has impact on when is the query evaluated 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>") }, f, :targetting_type) %>
         
     | 
| 
       99 
105 
     | 
    
         | 
| 
       100 
106 
     | 
    
         
             
                  <div class="col-md-4">
         
     | 
| 
       101 
107 
     | 
    
         
             
                    <%= radio_button_f targeting_fields, :targeting_type, :value => Targeting::STATIC_TYPE, :text => _(Targeting::TYPES[Targeting::STATIC_TYPE]) %>
         
     | 
| 
         @@ -2,7 +2,7 @@ 
     | 
|
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            <% title_actions(job_invocations_buttons) %>
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
     | 
    
         
            -
            <table class=" 
     | 
| 
      
 5 
     | 
    
         
            +
            <table class="<%= table_css_classes('table-condensed table-fixed') %>">
         
     | 
| 
       6 
6 
     | 
    
         
             
              <thead>
         
     | 
| 
       7 
7 
     | 
    
         
             
                <tr>
         
     | 
| 
       8 
8 
     | 
    
         
             
                  <th><%= sort :description, :as => _('Description') %></th>
         
     | 
| 
         @@ -7,7 +7,7 @@ 
     | 
|
| 
       7 
7 
     | 
    
         
             
                             link_to_function(_('Import'), 'show_import_job_template_modal();', :class => 'btn btn-default'),
         
     | 
| 
       8 
8 
     | 
    
         
             
                             new_link(_("New Job Template"))) %>
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
       10 
     | 
    
         
            -
            <table class=" 
     | 
| 
      
 10 
     | 
    
         
            +
            <table class="<%= table_css_classes('table-two-pane table-fixed') %>">
         
     | 
| 
       11 
11 
     | 
    
         
             
              <thead>
         
     | 
| 
       12 
12 
     | 
    
         
             
                <tr>
         
     | 
| 
       13 
13 
     | 
    
         
             
                  <th class="col-md-6"><%= sort :name, :as => s_("JobTemplate|Name") %></th>
         
     | 
| 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            <%= input_values_fields.fields_for input.id.to_s, composer.template_invocation_input_value_for(job_template, input) do |input_fields| %>
         
     | 
| 
       2 
2 
     | 
    
         
             
              <% unless input.options.blank? %>
         
     | 
| 
       3 
     | 
    
         
            -
                <%= selectable_f input_fields, :value, input.options_array, {:include_blank => !input.required}, : 
     | 
| 
      
 3 
     | 
    
         
            +
                <%= selectable_f input_fields, :value, input.options_array, { :include_blank => !input.required }, :label_help => input.description, :require => input.required, :label => input.name, :id => input.name, :onchange => "regenerate_description(this);" %>
         
     | 
| 
       4 
4 
     | 
    
         
             
              <% else %>
         
     | 
| 
       5 
     | 
    
         
            -
                <%= textarea_f input_fields, :value, :label => input.name, : 
     | 
| 
      
 5 
     | 
    
         
            +
                <%= textarea_f input_fields, :value, :label => input.name, :label_help => input.description, :required => input.required, :rows => 2, :onchange => "regenerate_description(this);", :id => input.name %>
         
     | 
| 
       6 
6 
     | 
    
         
             
              <% end %>
         
     | 
| 
       7 
7 
     | 
    
         
             
            <% end %>
         
     | 
| 
         @@ -9,7 +9,7 @@ 
     | 
|
| 
       9 
9 
     | 
    
         
             
                                 link_to_function(_('Toggle STDERR'), '$("div.line.stderr").toggle()', :class => 'btn btn-default'),
         
     | 
| 
       10 
10 
     | 
    
         
             
                                 link_to_function(_('Toggle STDOUT'), '$("div.line.stdout").toggle()', :class => 'btn btn-default'),
         
     | 
| 
       11 
11 
     | 
    
         
             
                                 link_to_function(_('Toggle DEBUG'), '$("div.line.debug").toggle()', :class => 'btn btn-default')) %>
         
     | 
| 
       12 
     | 
    
         
            -
                <%= button_group(template_invocation_task_buttons(@template_invocation_task)) %>
         
     | 
| 
      
 12 
     | 
    
         
            +
                <%= button_group(template_invocation_task_buttons(@template_invocation_task, @template_invocation.job_invocation)) %>
         
     | 
| 
       13 
13 
     | 
    
         
             
              </div>
         
     | 
| 
       14 
14 
     | 
    
         
             
            </div>
         
     | 
| 
       15 
15 
     | 
    
         | 
    
        data/config/routes.rb
    CHANGED
    
    | 
         @@ -24,6 +24,7 @@ Rails.application.routes.draw do 
     | 
|
| 
       24 
24 
     | 
    
         
             
                end
         
     | 
| 
       25 
25 
     | 
    
         
             
                member do
         
     | 
| 
       26 
26 
     | 
    
         
             
                  get 'rerun'
         
     | 
| 
      
 27 
     | 
    
         
            +
                  post 'cancel'
         
     | 
| 
       27 
28 
     | 
    
         
             
                end
         
     | 
| 
       28 
29 
     | 
    
         
             
              end
         
     | 
| 
       29 
30 
     | 
    
         | 
| 
         @@ -42,6 +43,10 @@ Rails.application.routes.draw do 
     | 
|
| 
       42 
43 
     | 
    
         
             
                    resources :hosts, :only => :none do
         
     | 
| 
       43 
44 
     | 
    
         
             
                      get '/', :to => 'job_invocations#output'
         
     | 
| 
       44 
45 
     | 
    
         
             
                    end
         
     | 
| 
      
 46 
     | 
    
         
            +
                    member do
         
     | 
| 
      
 47 
     | 
    
         
            +
                      post 'cancel'
         
     | 
| 
      
 48 
     | 
    
         
            +
                      post 'rerun'
         
     | 
| 
      
 49 
     | 
    
         
            +
                    end
         
     | 
| 
       45 
50 
     | 
    
         
             
                  end
         
     | 
| 
       46 
51 
     | 
    
         | 
| 
       47 
52 
     | 
    
         
             
                  resources :job_templates, :except => [:new, :edit] do
         
     | 
| 
         @@ -14,7 +14,7 @@ class ExpandAllTemplateInvocations < ActiveRecord::Migration[4.2] 
     | 
|
| 
       14 
14 
     | 
    
         
             
                FakeTemplateInvocation.update_all 'host_id = NULL'
         
     | 
| 
       15 
15 
     | 
    
         | 
| 
       16 
16 
     | 
    
         
             
                # expand all pattern template invocations and link RunHostJob
         
     | 
| 
       17 
     | 
    
         
            -
                JobInvocation.joins(:targeting).where("#{Targeting.table_name}.resolved_at IS NOT NULL").includes([ 
     | 
| 
      
 17 
     | 
    
         
            +
                JobInvocation.joins(:targeting).where("#{Targeting.table_name}.resolved_at IS NOT NULL").includes([:pattern_template_invocations, :targeting]).each do |job_invocation|
         
     | 
| 
       18 
18 
     | 
    
         
             
                  job_invocation.pattern_template_invocations.each do |pattern_template_invocation|
         
     | 
| 
       19 
19 
     | 
    
         
             
                    job_invocation.targeting.hosts.each do |host|
         
     | 
| 
       20 
20 
     | 
    
         
             
                      task = job_invocation.sub_tasks.find do |sub_task|
         
     | 
| 
         @@ -0,0 +1,6 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class AddFeatureIdToJobInvocation < ActiveRecord::Migration[4.2]
         
     | 
| 
      
 2 
     | 
    
         
            +
              def change
         
     | 
| 
      
 3 
     | 
    
         
            +
                add_column :job_invocations, :remote_execution_feature_id, :integer, :index => true
         
     | 
| 
      
 4 
     | 
    
         
            +
                add_foreign_key :job_invocations, :remote_execution_features, :column => :remote_execution_feature_id
         
     | 
| 
      
 5 
     | 
    
         
            +
              end
         
     | 
| 
      
 6 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,31 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class ChangeTaskIdToUuid < ActiveRecord::Migration[4.2]
         
     | 
| 
      
 2 
     | 
    
         
            +
              def up
         
     | 
| 
      
 3 
     | 
    
         
            +
                if on_postgresql?
         
     | 
| 
      
 4 
     | 
    
         
            +
                  change_table :job_invocations do |t|
         
     | 
| 
      
 5 
     | 
    
         
            +
                    t.change :task_id, :uuid, :using => 'task_id::uuid'
         
     | 
| 
      
 6 
     | 
    
         
            +
                  end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  change_table :template_invocations do |t|
         
     | 
| 
      
 9 
     | 
    
         
            +
                    t.change :run_host_job_task_id, :uuid, :using => 'run_host_job_task_id::uuid'
         
     | 
| 
      
 10 
     | 
    
         
            +
                  end
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              def down
         
     | 
| 
      
 15 
     | 
    
         
            +
                if on_postgresql?
         
     | 
| 
      
 16 
     | 
    
         
            +
                  change_table :job_invocations do |t|
         
     | 
| 
      
 17 
     | 
    
         
            +
                    t.change :task_id, :string, :limit => 255
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  change_table :template_invocations do |t|
         
     | 
| 
      
 21 
     | 
    
         
            +
                    t.change :run_host_job_task_id, :string, :limit => 255
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
              end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
              private
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
              def on_postgresql?
         
     | 
| 
      
 29 
     | 
    
         
            +
                ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
         
     | 
| 
      
 30 
     | 
    
         
            +
              end
         
     | 
| 
      
 31 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -26,7 +26,7 @@ Gem::Specification.new do |s| 
     | 
|
| 
       26 
26 
     | 
    
         
             
              s.add_dependency 'deface'
         
     | 
| 
       27 
27 
     | 
    
         
             
              s.add_dependency 'dynflow', '~> 0.8.26'
         
     | 
| 
       28 
28 
     | 
    
         
             
              s.add_dependency 'foreman_remote_execution_core'
         
     | 
| 
       29 
     | 
    
         
            -
              s.add_dependency 'foreman-tasks', ' 
     | 
| 
      
 29 
     | 
    
         
            +
              s.add_dependency 'foreman-tasks', '~> 0.12'
         
     | 
| 
       30 
30 
     | 
    
         | 
| 
       31 
31 
     | 
    
         
             
              s.add_development_dependency 'factory_bot_rails', '~> 4.8.0'
         
     | 
| 
       32 
32 
     | 
    
         
             
              s.add_development_dependency 'rubocop'
         
     | 
| 
         @@ -53,10 +53,11 @@ module ForemanRemoteExecution 
     | 
|
| 
       53 
53 
     | 
    
         
             
                                                           :'api/v2/job_templates' => [:destroy] }, :resource_type => 'JobTemplate'
         
     | 
| 
       54 
54 
     | 
    
         
             
                      permission :lock_job_templates, { :job_templates => [:lock, :unlock] }, :resource_type => 'JobTemplate'
         
     | 
| 
       55 
55 
     | 
    
         
             
                      permission :create_job_invocations, { :job_invocations => [:new, :create, :refresh, :rerun, :preview_hosts],
         
     | 
| 
       56 
     | 
    
         
            -
                                                            'api/v2/job_invocations' => [:create] }, :resource_type => 'JobInvocation'
         
     | 
| 
      
 56 
     | 
    
         
            +
                                                            'api/v2/job_invocations' => [:create, :rerun] }, :resource_type => 'JobInvocation'
         
     | 
| 
       57 
57 
     | 
    
         
             
                      permission :view_job_invocations, { :job_invocations => [:index, :show, :auto_complete_search], :template_invocations => [:show],
         
     | 
| 
       58 
58 
     | 
    
         
             
                                                          'api/v2/job_invocations' => [:index, :show, :output] }, :resource_type => 'JobInvocation'
         
     | 
| 
       59 
59 
     | 
    
         
             
                      permission :create_template_invocations, {}, :resource_type => 'TemplateInvocation'
         
     | 
| 
      
 60 
     | 
    
         
            +
                      permission :cancel_job_invocations, { :job_invocations => [:cancel], 'api/v2/job_invocations' => [:cancel] }, :resource_type => 'JobInvocation'
         
     | 
| 
       60 
61 
     | 
    
         
             
                      # this permissions grants user to get auto completion hints when setting up filters
         
     | 
| 
       61 
62 
     | 
    
         
             
                      permission :filter_autocompletion_for_template_invocation, { :template_invocations => [ :auto_complete_search, :index ] },
         
     | 
| 
       62 
63 
     | 
    
         
             
                                 :resource_type => 'TemplateInvocation'
         
     | 
| 
         @@ -71,6 +72,7 @@ module ForemanRemoteExecution 
     | 
|
| 
       71 
72 
     | 
    
         
             
                      :view_smart_proxies
         
     | 
| 
       72 
73 
     | 
    
         
             
                    ].freeze
         
     | 
| 
       73 
74 
     | 
    
         
             
                    MANAGER_PERMISSIONS = USER_PERMISSIONS + [
         
     | 
| 
      
 75 
     | 
    
         
            +
                      :cancel_job_invocations,
         
     | 
| 
       74 
76 
     | 
    
         
             
                      :destroy_job_templates,
         
     | 
| 
       75 
77 
     | 
    
         
             
                      :edit_job_templates,
         
     | 
| 
       76 
78 
     | 
    
         
             
                      :create_job_templates,
         
     | 
| 
         @@ -45,7 +45,13 @@ FactoryBot.define do 
     | 
|
| 
       45 
45 
     | 
    
         
             
                  after(:build) do |invocation, evaluator|
         
     | 
| 
       46 
46 
     | 
    
         
             
                    invocation.pattern_template_invocations << FactoryBot.build(:template_invocation)
         
     | 
| 
       47 
47 
     | 
    
         
             
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
       48 
49 
     | 
    
         | 
| 
      
 50 
     | 
    
         
            +
                trait :with_failed_task do
         
     | 
| 
      
 51 
     | 
    
         
            +
                  after(:build) do |invocation, _evaluator|
         
     | 
| 
      
 52 
     | 
    
         
            +
                    invocation.template_invocations << FactoryBot.build(:template_invocation, :with_failed_task, :with_host)
         
     | 
| 
      
 53 
     | 
    
         
            +
                    invocation.task = FactoryBot.build(:some_task)
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
       49 
55 
     | 
    
         
             
                end
         
     | 
| 
       50 
56 
     | 
    
         | 
| 
       51 
57 
     | 
    
         
             
                trait :with_task do
         
     | 
| 
         @@ -23,45 +23,77 @@ module Api 
     | 
|
| 
       23 
23 
     | 
    
         
             
                    assert_equal template['job_category'], @invocation.job_category
         
     | 
| 
       24 
24 
     | 
    
         
             
                  end
         
     | 
| 
       25 
25 
     | 
    
         | 
| 
       26 
     | 
    
         
            -
                   
     | 
| 
       27 
     | 
    
         
            -
                     
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
      
 26 
     | 
    
         
            +
                  context 'creation' do
         
     | 
| 
      
 27 
     | 
    
         
            +
                    setup do
         
     | 
| 
      
 28 
     | 
    
         
            +
                      @attrs = { :job_category => @template.job_category,
         
     | 
| 
      
 29 
     | 
    
         
            +
                                 :name => 'RandomName',
         
     | 
| 
      
 30 
     | 
    
         
            +
                                 :job_template_id => @template.id,
         
     | 
| 
      
 31 
     | 
    
         
            +
                                 :targeting_type => 'static_query',
         
     | 
| 
      
 32 
     | 
    
         
            +
                                 :search_query => 'foobar' }
         
     | 
| 
      
 33 
     | 
    
         
            +
                    end
         
     | 
| 
       30 
34 
     | 
    
         | 
| 
       31 
     | 
    
         
            -
                     
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
                    assert_response :success
         
     | 
| 
       34 
     | 
    
         
            -
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
                    test 'should create valid with job_template_id' do
         
     | 
| 
      
 36 
     | 
    
         
            +
                      post :create, params: { job_invocation: @attrs }
         
     | 
| 
       35 
37 
     | 
    
         | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
       39 
     | 
    
         
            -
                     
     | 
| 
      
 38 
     | 
    
         
            +
                      invocation = ActiveSupport::JSON.decode(@response.body)
         
     | 
| 
      
 39 
     | 
    
         
            +
                      assert_equal @attrs[:job_category], invocation['job_category']
         
     | 
| 
      
 40 
     | 
    
         
            +
                      assert_response :success
         
     | 
| 
      
 41 
     | 
    
         
            +
                    end
         
     | 
| 
       40 
42 
     | 
    
         | 
| 
       41 
     | 
    
         
            -
                     
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
      
 43 
     | 
    
         
            +
                    test 'should create with description format overridden' do
         
     | 
| 
      
 44 
     | 
    
         
            +
                      @attrs[:description_format] = 'format'
         
     | 
| 
      
 45 
     | 
    
         
            +
                      post :create, params: { job_invocation: @attrs }
         
     | 
| 
       44 
46 
     | 
    
         | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
       47 
     | 
    
         
            -
             
     | 
| 
       48 
     | 
    
         
            -
                              :search_query => 'foobar', :recurrence => {:cron_line => '5 * * * *'}}
         
     | 
| 
      
 47 
     | 
    
         
            +
                      invocation = ActiveSupport::JSON.decode(@response.body)
         
     | 
| 
      
 48 
     | 
    
         
            +
                      assert_equal @attrs[:description_format], invocation['description']
         
     | 
| 
      
 49 
     | 
    
         
            +
                    end
         
     | 
| 
       49 
50 
     | 
    
         | 
| 
       50 
     | 
    
         
            -
                     
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
       52 
     | 
    
         
            -
             
     | 
| 
       53 
     | 
    
         
            -
             
     | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
      
 51 
     | 
    
         
            +
                    test 'should create with recurrence' do
         
     | 
| 
      
 52 
     | 
    
         
            +
                      @attrs[:recurrence] = { cron_line: '5 * * * *' }
         
     | 
| 
      
 53 
     | 
    
         
            +
                      post :create, params: { job_invocation: @attrs }
         
     | 
| 
      
 54 
     | 
    
         
            +
                      invocation = ActiveSupport::JSON.decode(@response.body)
         
     | 
| 
      
 55 
     | 
    
         
            +
                      assert_equal invocation['mode'], 'recurring'
         
     | 
| 
      
 56 
     | 
    
         
            +
                      assert_response :success
         
     | 
| 
      
 57 
     | 
    
         
            +
                    end
         
     | 
| 
       55 
58 
     | 
    
         | 
| 
       56 
     | 
    
         
            -
             
     | 
| 
       57 
     | 
    
         
            -
             
     | 
| 
       58 
     | 
    
         
            -
             
     | 
| 
       59 
     | 
    
         
            -
             
     | 
| 
      
 59 
     | 
    
         
            +
                    test 'should create with schedule' do
         
     | 
| 
      
 60 
     | 
    
         
            +
                      @attrs[:scheduling] = { start_at: Time.now.to_s }
         
     | 
| 
      
 61 
     | 
    
         
            +
                      post :create, params: { job_invocation: @attrs }
         
     | 
| 
      
 62 
     | 
    
         
            +
                      invocation = ActiveSupport::JSON.decode(@response.body)
         
     | 
| 
      
 63 
     | 
    
         
            +
                      assert_equal invocation['mode'], 'future'
         
     | 
| 
      
 64 
     | 
    
         
            +
                      assert_response :success
         
     | 
| 
      
 65 
     | 
    
         
            +
                    end
         
     | 
| 
       60 
66 
     | 
    
         | 
| 
       61 
     | 
    
         
            -
                     
     | 
| 
       62 
     | 
    
         
            -
             
     | 
| 
       63 
     | 
    
         
            -
             
     | 
| 
       64 
     | 
    
         
            -
             
     | 
| 
      
 67 
     | 
    
         
            +
                    context 'with_feature' do
         
     | 
| 
      
 68 
     | 
    
         
            +
                      setup do
         
     | 
| 
      
 69 
     | 
    
         
            +
                        @feature = FactoryBot.create(:remote_execution_feature,
         
     | 
| 
      
 70 
     | 
    
         
            +
                                                     :job_template => @template)
         
     | 
| 
      
 71 
     | 
    
         
            +
                        @attrs = {
         
     | 
| 
      
 72 
     | 
    
         
            +
                          feature: @feature.label
         
     | 
| 
      
 73 
     | 
    
         
            +
                        }
         
     | 
| 
      
 74 
     | 
    
         
            +
                      end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                      test 'host ids as array of FQDNs' do
         
     | 
| 
      
 77 
     | 
    
         
            +
                        host = FactoryBot.create(:host)
         
     | 
| 
      
 78 
     | 
    
         
            +
                        @attrs[:host_ids] = [host.fqdn]
         
     | 
| 
      
 79 
     | 
    
         
            +
                        post :create, params: { job_invocation: @attrs }
         
     | 
| 
      
 80 
     | 
    
         
            +
                        assert_response :success
         
     | 
| 
      
 81 
     | 
    
         
            +
                      end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                      test 'host ids as array of IDs' do
         
     | 
| 
      
 84 
     | 
    
         
            +
                        host = FactoryBot.create(:host)
         
     | 
| 
      
 85 
     | 
    
         
            +
                        host2 = FactoryBot.create(:host)
         
     | 
| 
      
 86 
     | 
    
         
            +
                        @attrs[:host_ids] = [host.id, host2.id]
         
     | 
| 
      
 87 
     | 
    
         
            +
                        post :create, params: { job_invocation: @attrs }
         
     | 
| 
      
 88 
     | 
    
         
            +
                        assert_response :success
         
     | 
| 
      
 89 
     | 
    
         
            +
                      end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                      test 'search_query' do
         
     | 
| 
      
 92 
     | 
    
         
            +
                        @attrs[:host_ids] = 'name = testfqdn'
         
     | 
| 
      
 93 
     | 
    
         
            +
                        post :create, params: { job_invocation: @attrs }
         
     | 
| 
      
 94 
     | 
    
         
            +
                        assert_response :success
         
     | 
| 
      
 95 
     | 
    
         
            +
                      end
         
     | 
| 
      
 96 
     | 
    
         
            +
                    end
         
     | 
| 
       65 
97 
     | 
    
         
             
                  end
         
     | 
| 
       66 
98 
     | 
    
         | 
| 
       67 
99 
     | 
    
         
             
                  test 'should provide output for delayed task' do
         
     | 
| 
         @@ -74,6 +106,81 @@ module Api 
     | 
|
| 
       74 
106 
     | 
    
         
             
                    assert_equal result['output'], []
         
     | 
| 
       75 
107 
     | 
    
         
             
                    assert_response :success
         
     | 
| 
       76 
108 
     | 
    
         
             
                  end
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
                  test 'should cancel a job' do
         
     | 
| 
      
 111 
     | 
    
         
            +
                    @invocation.task.expects(:cancellable?).returns(true)
         
     | 
| 
      
 112 
     | 
    
         
            +
                    @invocation.task.expects(:cancel).returns(true)
         
     | 
| 
      
 113 
     | 
    
         
            +
                    JobInvocation.expects(:from_param).with(@invocation.id.to_s).returns(@invocation)
         
     | 
| 
      
 114 
     | 
    
         
            +
                    post :cancel, :params => { :id => @invocation.id }
         
     | 
| 
      
 115 
     | 
    
         
            +
                    result = ActiveSupport::JSON.decode(@response.body)
         
     | 
| 
      
 116 
     | 
    
         
            +
                    assert_equal result['cancelled'], true
         
     | 
| 
      
 117 
     | 
    
         
            +
                    assert_equal result['id'], @invocation.id
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
                    assert_response :success
         
     | 
| 
      
 120 
     | 
    
         
            +
                  end
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                  test 'should abort a job' do
         
     | 
| 
      
 123 
     | 
    
         
            +
                    @invocation.task.expects(:cancellable?).returns(true)
         
     | 
| 
      
 124 
     | 
    
         
            +
                    @invocation.task.expects(:abort).returns(true)
         
     | 
| 
      
 125 
     | 
    
         
            +
                    JobInvocation.expects(:from_param).with(@invocation.id.to_s).returns(@invocation)
         
     | 
| 
      
 126 
     | 
    
         
            +
                    post :cancel, :params => { :id => @invocation.id, :force => true }
         
     | 
| 
      
 127 
     | 
    
         
            +
                    result = ActiveSupport::JSON.decode(@response.body)
         
     | 
| 
      
 128 
     | 
    
         
            +
                    assert_equal result['cancelled'], true
         
     | 
| 
      
 129 
     | 
    
         
            +
                    assert_equal result['id'], @invocation.id
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                    assert_response :success
         
     | 
| 
      
 132 
     | 
    
         
            +
                  end
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                  test 'should error when trying to cancel a stopped job' do
         
     | 
| 
      
 135 
     | 
    
         
            +
                    @invocation.task.expects(:cancellable?).returns(false)
         
     | 
| 
      
 136 
     | 
    
         
            +
                    JobInvocation.expects(:from_param).with(@invocation.id.to_s).returns(@invocation)
         
     | 
| 
      
 137 
     | 
    
         
            +
                    post :cancel, :params => { :id => @invocation.id }
         
     | 
| 
      
 138 
     | 
    
         
            +
                    assert_response 422
         
     | 
| 
      
 139 
     | 
    
         
            +
                  end
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
                  test 'should rerun' do
         
     | 
| 
      
 142 
     | 
    
         
            +
                    JobInvocation.any_instance.expects(:generate_description)
         
     | 
| 
      
 143 
     | 
    
         
            +
                    JobInvocationComposer.any_instance
         
     | 
| 
      
 144 
     | 
    
         
            +
                                         .expects(:validate_job_category)
         
     | 
| 
      
 145 
     | 
    
         
            +
                                         .with(@invocation.job_category)
         
     | 
| 
      
 146 
     | 
    
         
            +
                                         .returns(@invocation.job_category)
         
     | 
| 
      
 147 
     | 
    
         
            +
                    post :rerun, params: { :id => @invocation.id }
         
     | 
| 
      
 148 
     | 
    
         
            +
                    assert_response :success
         
     | 
| 
      
 149 
     | 
    
         
            +
                    result = ActiveSupport::JSON.decode(@response.body)
         
     | 
| 
      
 150 
     | 
    
         
            +
                    targeting = Targeting.find(result['targeting_id'])
         
     | 
| 
      
 151 
     | 
    
         
            +
                    targeting.user_id.must_equal users(:admin).id
         
     | 
| 
      
 152 
     | 
    
         
            +
                    targeting.search_query.must_equal @invocation.targeting.search_query
         
     | 
| 
      
 153 
     | 
    
         
            +
                  end
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
                  test 'should not raise an exception when reruning failed has no hosts' do
         
     | 
| 
      
 156 
     | 
    
         
            +
                    JobInvocation.any_instance.expects(:generate_description)
         
     | 
| 
      
 157 
     | 
    
         
            +
                    JobInvocationComposer.any_instance
         
     | 
| 
      
 158 
     | 
    
         
            +
                                         .expects(:validate_job_category)
         
     | 
| 
      
 159 
     | 
    
         
            +
                                         .with(@invocation.job_category)
         
     | 
| 
      
 160 
     | 
    
         
            +
                                         .returns(@invocation.job_category)
         
     | 
| 
      
 161 
     | 
    
         
            +
                    post :rerun, params: { :id => @invocation.id, :failed_only => true }
         
     | 
| 
      
 162 
     | 
    
         
            +
                    assert_response :success
         
     | 
| 
      
 163 
     | 
    
         
            +
                    result = ActiveSupport::JSON.decode(@response.body)
         
     | 
| 
      
 164 
     | 
    
         
            +
                    targeting = Targeting.find(result['targeting_id'])
         
     | 
| 
      
 165 
     | 
    
         
            +
                    targeting.user_id.must_equal users(:admin).id
         
     | 
| 
      
 166 
     | 
    
         
            +
                    targeting.search_query.must_equal 'name ^ ()'
         
     | 
| 
      
 167 
     | 
    
         
            +
                  end
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
| 
      
 169 
     | 
    
         
            +
                  test 'should rerun failed only' do
         
     | 
| 
      
 170 
     | 
    
         
            +
                    @invocation = FactoryBot.create(:job_invocation, :with_template, :with_failed_task)
         
     | 
| 
      
 171 
     | 
    
         
            +
                    JobInvocation.any_instance.expects(:generate_description)
         
     | 
| 
      
 172 
     | 
    
         
            +
                    JobInvocationComposer.any_instance
         
     | 
| 
      
 173 
     | 
    
         
            +
                                         .expects(:validate_job_category)
         
     | 
| 
      
 174 
     | 
    
         
            +
                                         .with(@invocation.job_category)
         
     | 
| 
      
 175 
     | 
    
         
            +
                                         .returns(@invocation.job_category)
         
     | 
| 
      
 176 
     | 
    
         
            +
                    post :rerun, params: { :id => @invocation.id, :failed_only => true }
         
     | 
| 
      
 177 
     | 
    
         
            +
                    assert_response :success
         
     | 
| 
      
 178 
     | 
    
         
            +
                    result = ActiveSupport::JSON.decode(@response.body)
         
     | 
| 
      
 179 
     | 
    
         
            +
                    targeting = Targeting.find(result['targeting_id'])
         
     | 
| 
      
 180 
     | 
    
         
            +
                    hostnames = @invocation.template_invocations.map { |ti| ti.host.name }
         
     | 
| 
      
 181 
     | 
    
         
            +
                    targeting.user_id.must_equal users(:admin).id
         
     | 
| 
      
 182 
     | 
    
         
            +
                    targeting.search_query.must_equal "name ^ (#{hostnames.join(',')})"
         
     | 
| 
      
 183 
     | 
    
         
            +
                  end
         
     | 
| 
       77 
184 
     | 
    
         
             
                end
         
     | 
| 
       78 
185 
     | 
    
         
             
              end
         
     | 
| 
       79 
186 
     | 
    
         
             
            end
         
     |