foreman_remote_execution 0.0.4 → 0.0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c8ad2a2f768b914a79f715a13d1a653092d6c4a0
4
- data.tar.gz: fe10900091a3fd6817f59bba6b1548cd97cb90ef
3
+ metadata.gz: 66bde99bb7667a338eddcafc4ace45a5a033b8bd
4
+ data.tar.gz: d55a2a2d6789076c01948424f8a9548e7ed1d2de
5
5
  SHA512:
6
- metadata.gz: af70b52d0cf04a10ae1da4992b65c767e2e97b00ed7cb0f59404f0801c3b98c0c4a7188d0d7ee8fec02034d49d4dea7165621af94626d4123f1f0462e62ea277
7
- data.tar.gz: 544b92e0a6d36e2553bd9005d57c6cb37e71a2069951a93f5346cba8b395d85dfcac75abbd368280970cc4170807ae0904da6dc2140588710ca940391b279c16
6
+ metadata.gz: b99fca6b8d6d9f576cc42414f6c37e91b2121887cf696692d2beec4264ac776c4d2f16c983366c914917023724ce2e8548b1ef6caeef7b7883f10380f8bba344
7
+ data.tar.gz: 85dd26a1fbbc873a0a0493672922d73067d5d73cf33d610115357a3abd59570776bb6cc06ea38b32986f08b64c914db146a61561c81718e5c1b056989dc1342c
@@ -19,6 +19,11 @@ function refresh_execution_form() {
19
19
  });
20
20
  }
21
21
 
22
+ function refresh_search_query(value){
23
+ id = value.val;
24
+ $('textarea#targeting_search_query').val($('span#bookmark_query_map span#bookmark-' + id).data('query'));
25
+ }
26
+
22
27
  function job_invocation_form_binds() {
23
28
  $('input.job_template_selector').on('click', function () {
24
29
  parent_fieldset = $(this).closest('fieldset');
@@ -29,4 +34,8 @@ function job_invocation_form_binds() {
29
34
  $('select#job_invocation_job_name').on('change', refresh_execution_form);
30
35
 
31
36
  $('button#refresh_execution_form').on('click', refresh_execution_form);
37
+
38
+ $('textarea#targeting_search_query').on('change', refresh_execution_form);
39
+
40
+ $('select#targeting_bookmark_id').on('change', refresh_search_query);
32
41
  }
@@ -2,16 +2,28 @@ class JobInvocationsController < ApplicationController
2
2
  include Foreman::Controller::AutoCompleteSearch
3
3
 
4
4
  def new
5
- @composer = JobInvocationComposer.new(JobInvocation.new,
6
- :host_ids => params[:host_ids],
7
- :targeting => {
8
- :targeting_type => Targeting::STATIC_TYPE,
9
- :bookmark_id => params[:bookmark_id]
10
- })
5
+ @composer = JobInvocationComposer.new.compose_from_params(
6
+ :host_ids => params[:host_ids],
7
+ :targeting => {
8
+ :targeting_type => Targeting::STATIC_TYPE,
9
+ :bookmark_id => params[:bookmark_id]
10
+ })
11
+ end
12
+
13
+ def rerun
14
+ job_invocation = resource_base.find(params[:id])
15
+ @composer = JobInvocationComposer.new.compose_from_invocation(job_invocation)
16
+
17
+ if params[:failed_only]
18
+ host_ids = job_invocation.failed_host_ids
19
+ @composer.search_query = @composer.targeting.build_query_from_hosts(host_ids)
20
+ end
21
+
22
+ render :action => 'new'
11
23
  end
12
24
 
13
25
  def create
14
- @composer = JobInvocationComposer.new(JobInvocation.new, params)
26
+ @composer = JobInvocationComposer.new.compose_from_params(params)
15
27
  if @composer.save
16
28
  @task = ForemanTasks.async_task(::Actions::RemoteExecution::RunHostsJob, @composer.job_invocation)
17
29
  redirect_to job_invocation_path(@composer.job_invocation)
@@ -30,6 +42,17 @@ class JobInvocationsController < ApplicationController
30
42
 
31
43
  # refreshes the form
32
44
  def refresh
33
- @composer = JobInvocationComposer.new(JobInvocation.new, params)
45
+ @composer = JobInvocationComposer.new.compose_from_params(params)
46
+ end
47
+
48
+ private
49
+
50
+ def action_permission
51
+ case params[:action]
52
+ when 'rerun'
53
+ 'create'
54
+ else
55
+ super
56
+ end
34
57
  end
35
58
  end
@@ -57,6 +57,16 @@ module RemoteExecutionHelper
57
57
  def job_invocation_task_buttons(task)
58
58
  buttons = []
59
59
  buttons << link_to(_('Refresh'), {}, :class => 'btn btn-default', :title => _('Refresh this page'))
60
+ if authorized_for(:permission => :create_job_invocations)
61
+ buttons << link_to(_("Rerun"), rerun_job_invocation_path(:id => task.locks.where(:resource_type => 'JobInvocation').first.resource),
62
+ :class => "btn btn-default",
63
+ :title => _('Rerun the job'))
64
+ end
65
+ if authorized_for(:permission => :create_job_invocations)
66
+ buttons << link_to(_("Rerun failed"), rerun_job_invocation_path(:id => task.locks.where(:resource_type => 'JobInvocation').first.resource, :failed_only => 1),
67
+ :class => "btn btn-default",
68
+ :title => _('Rerun on failed hosts'))
69
+ end
60
70
  if authorized_for(:permission => :view_foreman_tasks, :auth_object => task)
61
71
  buttons << link_to(_("Last Job Task"), foreman_tasks_task_path(task),
62
72
  :class => "btn btn-default",
@@ -18,4 +18,28 @@ class JobInvocation < ActiveRecord::Base
18
18
  def to_action_input
19
19
  { :id => id, :name => job_name }
20
20
  end
21
+
22
+ def template_invocations_tasks
23
+ if last_task.present?
24
+ last_task.sub_tasks.for_action_types('Actions::RemoteExecution::RunHostJob')
25
+ else
26
+ ForemanTasks::Task.for_action_types('Actions::RemoteExecution::RunHostJob').where('1=0')
27
+ end
28
+ end
29
+
30
+ def failed_template_invocation_tasks
31
+ template_invocations_tasks.where(:result => 'warning')
32
+ end
33
+
34
+ def failed_host_ids
35
+ locks_for_resource(failed_template_invocation_tasks, 'Host::Managed').map(&:resource_id)
36
+ end
37
+
38
+ def failed_hosts
39
+ locks_for_resource(failed_template_invocation_tasks, 'Host::Managed').map(&:resource)
40
+ end
41
+
42
+ def locks_for_resource(tasks, resource_type)
43
+ tasks.map { |task| task.locks.where(:resource_type => resource_type).first }.compact
44
+ end
21
45
  end
@@ -3,8 +3,11 @@ class JobInvocationComposer
3
3
  attr_reader :job_template_ids
4
4
  delegate :job_name, :targeting, :to => :job_invocation
5
5
 
6
- def initialize(job_invocation, params)
6
+ def initialize(job_invocation = JobInvocation.new)
7
7
  @job_invocation = job_invocation
8
+ end
9
+
10
+ def compose_from_params(params)
8
11
  @params = params
9
12
 
10
13
  @host_ids = validate_host_ids(params[:host_ids])
@@ -15,6 +18,19 @@ class JobInvocationComposer
15
18
  job_invocation.targeting = build_targeting
16
19
 
17
20
  @job_template_ids = validate_job_template_ids(job_templates_base.keys.compact)
21
+ self
22
+ end
23
+
24
+ def compose_from_invocation(invocation)
25
+ @params = {}
26
+
27
+ job_invocation.job_name = validate_job_name(invocation.job_name)
28
+ job_invocation.targeting = invocation.targeting.dup
29
+ @search_query = targeting.search_query unless targeting.bookmark_id.present?
30
+
31
+ @job_template_ids = invocation.template_invocations.map(&:template_id)
32
+ @template_invocations = dup_template_invocations(invocation)
33
+ self
18
34
  end
19
35
 
20
36
  def valid?
@@ -117,6 +133,14 @@ class JobInvocationComposer
117
133
 
118
134
  private
119
135
 
136
+ def dup_template_invocations(job_invocation)
137
+ job_invocation.template_invocations.map do |template_invocation|
138
+ duplicate = template_invocation.dup
139
+ template_invocation.input_values.map { |value| duplicate.input_values.build :value => value.value, :template_input_id => value.template_input_id }
140
+ duplicate
141
+ end
142
+ end
143
+
120
144
  def targeting_base
121
145
  @params.fetch(:targeting, {})
122
146
  end
@@ -37,14 +37,53 @@ class JobTemplate < ::Template
37
37
  end
38
38
  self.table_name = 'templates'
39
39
 
40
+ # Import a template ERB, with metadata in the first YAML comment
41
+ def self.import(template, options = {})
42
+ metadata = parse_metadata(template)
43
+ return if metadata.blank? || metadata.delete('kind') != 'job_template' || self.find_by_name(metadata['name'])
44
+
45
+ inputs = metadata.delete('template_inputs')
46
+
47
+ # This awkward dance is because you can't instantiate a new template with :locked => true
48
+ template = self.create(metadata.merge(:template => template.gsub(/<%\#.+?.-?%>\n?/m, '')))
49
+ template.update_attributes(options)
50
+ template.assign_taxonomies
51
+
52
+ inputs.each do |input|
53
+ template.template_inputs << TemplateInput.create(input)
54
+ end
55
+
56
+ template
57
+ end
58
+
40
59
  # Override method in Taxonomix as Template is not used attached to a Host,
41
60
  # and matching a Host does not prevent removing a template from its taxonomy.
42
61
  def used_taxonomy_ids(type)
43
62
  []
44
63
  end
45
64
 
65
+ def dup
66
+ dup = super
67
+ self.template_inputs.each do |input|
68
+ dup.template_inputs.build input.attributes.except('template_id', 'id', 'created_at', 'updated_at')
69
+ end
70
+ dup
71
+ end
72
+
73
+ def assign_taxonomies
74
+ if default
75
+ organizations << Organization.all if SETTINGS[:organizations_enabled]
76
+ locations << Location.all if SETTINGS[:locations_enabled]
77
+ end
78
+ end
79
+
46
80
  private
47
81
 
82
+ def self.parse_metadata(template)
83
+ match = template.match(/<%\#(.+?).-?%>/m)
84
+ match.nil? ? {} : YAML.load(match[1])
85
+ end
86
+
48
87
  # we can't use standard validator, .provider_names output can change but the validator does not reflect it
49
88
  def provider_type_whitelist
50
89
  errors.add :provider_type, :uniq unless RemoteExecutionProvider.provider_names.include?(self.provider_type)
@@ -6,7 +6,8 @@ class TemplateInput < ActiveRecord::Base
6
6
  :puppet_parameter => N_('Puppet parameter') }.with_indifferent_access
7
7
 
8
8
  attr_accessible :name, :required, :input_type, :fact_name, :variable_name,
9
- :puppet_class_name, :puppet_parameter_name, :description, :job_template_id
9
+ :puppet_class_name, :puppet_parameter_name, :description, :job_template_id,
10
+ :options
10
11
 
11
12
  belongs_to :template
12
13
  has_many :template_invocation_input_values, :dependent => :destroy
@@ -42,6 +43,10 @@ class TemplateInput < ActiveRecord::Base
42
43
  get_resolver(renderer).value
43
44
  end
44
45
 
46
+ def options_array
47
+ self.options.blank? ? [] : self.options.split(/\r?\n/).map(&:strip)
48
+ end
49
+
45
50
  private
46
51
 
47
52
  def get_resolver(renderer)
@@ -5,4 +5,6 @@ class TemplateInvocationInputValue < ActiveRecord::Base
5
5
 
6
6
  validates :value, :presence => true, :if => proc { |v| v.template_input.required? }
7
7
 
8
+ validates :value, :inclusion => { :in => proc { |v| v.template_input.options_array } },
9
+ :if => proc { |v| v.template_input.input_type == 'user' && v.template_input.options_array.present? }
8
10
  end
@@ -2,7 +2,12 @@
2
2
  <%= selectable_f f, :job_name, @composer.available_job_names %>
3
3
 
4
4
  <%= fields_for @composer.targeting do |targeting_fields| %>
5
- <%= targeting_fields.hidden_field :bookmark_id, :value => params[:bookmark_id] %>
5
+ <span id="bookmark_query_map" >
6
+ <% @composer.available_bookmarks.each do |bookmark| %>
7
+ <span id="bookmark-<%= bookmark.id %>" data-query="<%= bookmark.query %>"></span>
8
+ <% end %>
9
+ </span>
10
+ <%= selectable_f targeting_fields, :bookmark_id, @composer.available_bookmarks.map {|b| [ b.name, b.id ] }, :selected => @composer.targeting.bookmark_id, :include_blank => true %>
6
11
  <%= textarea_f targeting_fields, :search_query, :value => @composer.displayed_search_query, :rows => 5 %>
7
12
 
8
13
  <div class="form-group ">
@@ -49,7 +54,11 @@
49
54
  <%= job_template_fields.fields_for :input_values do |input_values_fields| %>
50
55
  <% job_template.template_inputs.where(:input_type => 'user').each do |input| %>
51
56
  <%= 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 %>
57
+ <% unless input.options.blank? %>
58
+ <%= selectable_f input_fields, :value, input.options_array, {:include_blank => !input.required }, :require => input.required, :label => input.name, :help_inline => input.description %>
59
+ <% else %>
60
+ <%= textarea_f input_fields, :value, :label => input.name, :help_inline => input.description, :required => input.required, :rows => 2 %>
61
+ <% end %>
53
62
  <% end %>
54
63
  <% end %>
55
64
  <% end %>
@@ -27,7 +27,17 @@
27
27
  <h5>
28
28
  <b><%= template_invocation.template.name %></b> <%= 'through' %> <%= _(RemoteExecutionProvider.provider_for(template_invocation.template.provider_type)) %>
29
29
  </h5>
30
- <pre><%= InputTemplateRenderer.new(template_invocation.template, nil, template_invocation).preview %></pre>
30
+ <% target = template_invocation.targeting.hosts.with_os.first || template_invocation.targeting.hosts.first %>
31
+ <%= _('Preview for target %s') % target.try(:name) || 'N/A' %>
32
+
33
+ <% renderer = InputTemplateRenderer.new(template_invocation.template, target, template_invocation) %>
34
+ <% if (preview = renderer.preview) %>
35
+ <pre><%= preview %></pre>
36
+ <% else %>
37
+ <%= alert :class => "alert-block alert-danger base in fade has-error",
38
+ :text => renderer.error_message.html_safe %>
39
+ <% end %>
40
+
31
41
 
32
42
  <% if template_invocation.input_values.present? %>
33
43
  <%= _('following user inputs were provided') %>
@@ -16,6 +16,9 @@
16
16
  <%= text_f f, :puppet_class_name, :class => 'puppet_parameter_input_type', :required => true %>
17
17
  <%= text_f f, :puppet_parameter_name, :class => 'puppet_parameter_input_type', :required => true %>
18
18
  </div>
19
+ <div class="user_input_type custom_input_type_fields" style="<%= (f.object.user_template_input? || f.object.new_record?) ? '' : 'display:none' %>">
20
+ <%= textarea_f f, :options, :rows => 3, :class => 'user_input_type', :help_inline => _("A list of options the user can select from. If not provided, the user will be given a free-form field") %>
21
+ </div>
19
22
  <%= textarea_f f, :description, :rows => 3 %>
20
23
  <% end %>
21
24
  </div>
@@ -0,0 +1,52 @@
1
+ <%#
2
+ kind: job_template
3
+ name: Package Action - SSH Default
4
+ job_name: Package Action
5
+ provider_type: Ssh
6
+ template_inputs:
7
+ - name: pre_script
8
+ description: A script to run prior to the package action
9
+ input_type: user
10
+ required: false
11
+ - name: action
12
+ description: 'The package action: install, update, or remove'
13
+ input_type: user
14
+ required: true
15
+ options: "install\nupdate\nremove"
16
+ - name: package
17
+ description: The name of the package, if any
18
+ input_type: user
19
+ required: false
20
+ - name: post_script
21
+ description: A script to run after the package action
22
+ input_type: user
23
+ required: false
24
+ %>
25
+
26
+ die() {
27
+ echo "${1}, exiting..."
28
+ exit $2
29
+ }
30
+
31
+ <% unless input("pre_script").blank? -%>
32
+ # Pre Script
33
+ <%= input("pre_script") %>
34
+ RETVAL=$?
35
+ [ $RETVAL -eq 0 ] || die "Pre script failed" $RETVAL
36
+ <% end -%>
37
+
38
+ # Action
39
+ <% if @host.operatingsystem.family == 'Redhat' -%>
40
+ yum -y <%= input("action") %> <%= input("package") %>
41
+ <% elsif @host.operatingsystem.family == 'Debian' -%>
42
+ apt-get -y <%= input("action") %> <%= input("package") %>
43
+ <% end -%>
44
+ RETVAL=$?
45
+ [ $RETVAL -eq 0 ] || die "Package action failed" $RETVAL
46
+
47
+ <% unless input("post_script").blank? -%>
48
+ # Post Script
49
+ <%= input("post_script") %>
50
+ RETVAL=$?
51
+ [ $RETVAL -eq 0 ] || die "Post script failed" $RETVAL
52
+ <% end -%>
@@ -0,0 +1,12 @@
1
+ <%#
2
+ kind: job_template
3
+ name: Puppet Run Once - SSH Default
4
+ job_name: Puppet Run Once
5
+ provider_type: Ssh
6
+ template_inputs:
7
+ - name: puppet_options
8
+ description: Additional options to pass to puppet
9
+ input_type: user
10
+ required: false
11
+ %>
12
+ puppet agent --onetime --no-usecacheonfailure <%= input("puppet_options") %>
@@ -0,0 +1,12 @@
1
+ <%#
2
+ kind: job_template
3
+ name: Run Command - SSH Default
4
+ job_name: Run Command
5
+ provider_type: Ssh
6
+ template_inputs:
7
+ - name: command
8
+ description: Command to run on the host
9
+ input_type: user
10
+ required: true
11
+ %>
12
+ <%= input("command") %>
@@ -0,0 +1,21 @@
1
+ <%#
2
+ kind: job_template
3
+ name: Service Action - SSH Default
4
+ job_name: Service Action
5
+ provider_type: Ssh
6
+ template_inputs:
7
+ - name: action
8
+ description: Action to perform on the service
9
+ input_type: user
10
+ options: "restart\nstart\nstop\nstatus"
11
+ required: true
12
+ - name: service
13
+ description: Name of the service
14
+ input_type: user
15
+ required: true
16
+ %>
17
+ <% if @host.operatingsystem.family == "Redhat" && @host.operatingsystem.major.to_i > 6 %>
18
+ systemctl <%= input("action") %> <%= input("service") %>
19
+ <% else %>
20
+ service <%= input("service") %> <%= input("action") %>
21
+ <% end -%>
data/config/routes.rb CHANGED
@@ -18,6 +18,9 @@ Rails.application.routes.draw do
18
18
  post 'refresh'
19
19
  get 'auto_complete_search'
20
20
  end
21
+ member do
22
+ get 'rerun'
23
+ end
21
24
  end
22
25
 
23
26
  namespace :api, :defaults => {:format => 'json'} do
@@ -0,0 +1,5 @@
1
+ class ChangeTargetingSearchQueryType < ActiveRecord::Migration
2
+ def change
3
+ change_column :targetings, :search_query, :text
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddOptionsToTemplateInput < ActiveRecord::Migration
2
+ def change
3
+ add_column :template_inputs, :options, :text
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ User.as_anonymous_admin do
2
+ JobTemplate.without_auditing do
3
+ Dir[File.join("#{ForemanRemoteExecution::Engine.root}/app/views/templates/**/*.erb")].each do |template|
4
+ JobTemplate.import(File.read(template), :default => true, :locked => true)
5
+ end
6
+ end
7
+ end
@@ -45,7 +45,7 @@ module ForemanRemoteExecution
45
45
 
46
46
  permission :view_job_invocations, { :job_invocations => [:index, :show, :auto_complete_search] }, :resource_type => 'JobInvocation'
47
47
 
48
- permission :create_job_invocations, { :job_invocations => [:new, :create, :refresh] }, :resource_type => 'JobInvocation'
48
+ permission :create_job_invocations, { :job_invocations => [:new, :create, :refresh, :rerun] }, :resource_type => 'JobInvocation'
49
49
  end
50
50
 
51
51
  # Add a new role called 'ForemanRemoteExecution' if it doesn't exist
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '0.0.4'
2
+ VERSION = '0.0.5'
3
3
  end
@@ -84,6 +84,33 @@ describe InputTemplateRenderer do
84
84
  end
85
85
  end
86
86
  end
87
+
88
+ context 'with options specified' do
89
+
90
+ let(:job_invocation) { FactoryGirl.create(:job_invocation) }
91
+ let(:template_invocation) { FactoryGirl.build(:template_invocation, :template => template) }
92
+ let(:result) { renderer.render }
93
+
94
+ before do
95
+ template.template_inputs << FactoryGirl.build(:template_input, :name => 'service_name', :input_type => 'user', :options => "httpd\nforeman")
96
+ end
97
+
98
+ context 'with a valid input defined' do
99
+ before do
100
+ job_invocation.template_invocations << template_invocation
101
+ renderer.invocation = template_invocation
102
+
103
+ FactoryGirl.create(:template_invocation_input_value,
104
+ :template_invocation => template_invocation,
105
+ :template_input => template.template_inputs.first,
106
+ :value => 'foreman')
107
+ end
108
+
109
+ it 'can render with job invocation with corresponding value' do
110
+ renderer.render.must_equal 'service restart foreman'
111
+ end
112
+ end
113
+ end
87
114
  end
88
115
 
89
116
  context "renderer for template with fact input used" do
@@ -39,7 +39,7 @@ describe JobInvocationComposer do
39
39
  context 'with general new invocation and empty params' do
40
40
  let(:params) { {} }
41
41
  let(:job_invocation) { JobInvocation.new }
42
- let(:composer) { JobInvocationComposer.new(job_invocation, params) }
42
+ let(:composer) { JobInvocationComposer.new(job_invocation).compose_from_params(params) }
43
43
 
44
44
  describe '#available_templates' do
45
45
  it 'obeys authorization' do
@@ -369,7 +369,9 @@ describe JobInvocationComposer do
369
369
  :input_values => { input1.id.to_s => { :value => 'value1' } }
370
370
  } } }
371
371
  end
372
- let(:params) { { :job_invocation => { :providers => { :ssh => ssh_params } }, :targeting => { :search_query => "name = #{host.name}" } }.with_indifferent_access }
372
+ let(:params) do
373
+ { :job_invocation => { :providers => { :ssh => ssh_params } }, :targeting => { :search_query => "name = #{host.name}" } }.with_indifferent_access
374
+ end
373
375
 
374
376
  it 'validates all associated objects even if some of the is invalid' do
375
377
  composer
@@ -410,6 +412,52 @@ describe JobInvocationComposer do
410
412
  end
411
413
  end
412
414
 
415
+ describe '#compose_from_invocation(existing_invocation)' do
416
+ let(:host) { FactoryGirl.create(:host) }
417
+ let(:ssh_params) do
418
+ { :job_template_id => testing_job_template_1.id.to_s,
419
+ :job_templates => {
420
+ testing_job_template_1.id.to_s => {
421
+ :input_values => { input1.id.to_s => { :value => 'value1' } }
422
+ } } }
423
+ end
424
+ let(:params) do
425
+ {
426
+ :job_invocation => {
427
+ :providers => { :ssh => ssh_params }
428
+ },
429
+ :targeting => {
430
+ :search_query => "name = #{host.name}",
431
+ :targeting_type => Targeting::STATIC_TYPE
432
+ }
433
+ }.with_indifferent_access
434
+ end
435
+ let(:existing) { job_invocation.reload }
436
+ let(:new_job_invocation) { JobInvocation.new }
437
+ let(:new_composer) { JobInvocationComposer.new(new_job_invocation).compose_from_invocation(job_invocation) }
438
+
439
+ before do
440
+ composer.save
441
+ end
442
+
443
+ it 'sets the same job name' do
444
+ new_composer.job_name.must_equal existing.job_name
445
+ end
446
+
447
+ it 'builds new targeting object which keeps search query' do
448
+ new_composer.targeting.wont_equal existing.targeting
449
+ new_composer.search_query.must_equal existing.targeting.search_query
450
+ end
451
+
452
+ it 'keeps job template ids' do
453
+ new_composer.job_template_ids.must_equal existing.template_invocations.map(&:template_id)
454
+ end
455
+
456
+ it 'keeps template invocations and their values' do
457
+ new_composer.template_invocations.size.must_equal existing.template_invocations.size
458
+ end
459
+
460
+ end
413
461
  end
414
462
  end
415
463
  end
@@ -1,5 +1,54 @@
1
1
  require 'test_plugin_helper'
2
2
 
3
3
  describe JobTemplate do
4
+ context 'cloning' do
5
+ let(:job_template) { FactoryGirl.build(:job_template, :with_input) }
4
6
 
7
+ describe '#dup' do
8
+ it 'duplicates also template inputs' do
9
+ duplicate = job_template.dup
10
+ duplicate.wont_equal job_template
11
+ duplicate.template_inputs.wont_be_empty
12
+ duplicate.template_inputs.first.wont_equal job_template.template_inputs.first
13
+ duplicate.template_inputs.first.name.must_equal job_template.template_inputs.first.name
14
+ end
15
+ end
16
+ end
17
+
18
+ context 'importing a template' do
19
+ let(:template) do
20
+ template = <<-END_TEMPLATE
21
+ <%#
22
+ kind: job_template
23
+ name: Service Restart
24
+ job_name: Service Restart
25
+ provider_type: Ssh
26
+ template_inputs:
27
+ - name: service_name
28
+ input_type: user
29
+ required: true
30
+ %>
31
+
32
+ service <%= input("service_name") %> restart
33
+ END_TEMPLATE
34
+
35
+ JobTemplate.import(template, :default => true)
36
+ end
37
+
38
+ it 'sets the name' do
39
+ template.name.must_equal 'Service Restart'
40
+ end
41
+
42
+ it 'has a template' do
43
+ template.template.squish.must_equal 'service <%= input("service_name") %> restart'
44
+ end
45
+
46
+ it 'imports inputs' do
47
+ template.template_inputs.first.name.must_equal 'service_name'
48
+ end
49
+
50
+ it 'sets additional options' do
51
+ template.default.must_equal true
52
+ end
53
+ end
5
54
  end
@@ -0,0 +1,29 @@
1
+ require 'test_plugin_helper'
2
+
3
+ describe TemplateInvocationInputValue do
4
+ let(:template) { FactoryGirl.build(:job_template, :template => 'service restart <%= input("service_name") -%>') }
5
+ let(:renderer) { InputTemplateRenderer.new(template) }
6
+ let(:job_invocation) { FactoryGirl.create(:job_invocation) }
7
+ let(:template_invocation) { FactoryGirl.build(:template_invocation, :template => template) }
8
+ let(:result) { renderer.render }
9
+
10
+ context 'with selectable options' do
11
+ before do
12
+ result # let is lazy
13
+ template.template_inputs << FactoryGirl.build(:template_input, :name => 'service_name', :input_type => 'user',
14
+ :required => true, :options => "foreman\nhttpd")
15
+ end
16
+
17
+ it 'fails with an invalid option' do
18
+ refute_valid FactoryGirl.build(:template_invocation_input_value, :template_invocation => template_invocation,
19
+ :template_input => template.template_inputs.first,
20
+ :value => 'sendmail')
21
+ end
22
+
23
+ it 'succeeds with valid option' do
24
+ assert_valid FactoryGirl.build(:template_invocation_input_value, :template_invocation => template_invocation,
25
+ :template_input => template.template_inputs.first,
26
+ :value => 'foreman')
27
+ end
28
+ end
29
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_remote_execution
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Foreman Remote Execution team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-26 00:00:00.000000000 Z
11
+ date: 2015-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -161,6 +161,10 @@ files:
161
161
  - app/views/job_templates/index.html.erb
162
162
  - app/views/job_templates/new.html.erb
163
163
  - app/views/template_inputs/_form.html.erb
164
+ - app/views/templates/package_action.erb
165
+ - app/views/templates/puppet_run_once.erb
166
+ - app/views/templates/run_command.erb
167
+ - app/views/templates/service_action.erb
164
168
  - config/routes.rb
165
169
  - db/migrate/20150612121541_add_job_template_to_template.rb
166
170
  - db/migrate/20150616080015_create_template_input.rb
@@ -169,7 +173,10 @@ files:
169
173
  - db/migrate/20150708133305_add_template_invocation.rb
170
174
  - db/migrate/20150812110800_add_resolved_at_to_targeting.rb
171
175
  - db/migrate/20150812145900_add_last_task_id_to_job_invocation.rb
176
+ - db/migrate/20150827144500_change_targeting_search_query_type.rb
177
+ - db/migrate/20150827152730_add_options_to_template_input.rb
172
178
  - db/seeds.d/60-ssh_proxy_feature.rb
179
+ - db/seeds.d/70-job_templates.rb
173
180
  - doc/Gemfile
174
181
  - doc/Gemfile.lock
175
182
  - doc/Rakefile
@@ -223,6 +230,7 @@ files:
223
230
  - test/unit/remote_execution_provider_test.rb
224
231
  - test/unit/targeting_test.rb
225
232
  - test/unit/template_input_test.rb
233
+ - test/unit/template_invocation_input_value_test.rb
226
234
  homepage: https://github.com/theforeman/foreman_remote_execution
227
235
  licenses: []
228
236
  metadata: {}
@@ -250,6 +258,7 @@ summary: A plugin bringing remote execution to the Foreman, completing the confi
250
258
  test_files:
251
259
  - test/test_plugin_helper.rb
252
260
  - test/unit/job_template_test.rb
261
+ - test/unit/template_invocation_input_value_test.rb
253
262
  - test/unit/template_input_test.rb
254
263
  - test/unit/job_invocation_test.rb
255
264
  - test/unit/targeting_test.rb