foreman_remote_execution 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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