foreman_remote_execution 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +7 -0
  4. data/.rubocop_todo.yml +16 -5
  5. data/.tx/config +1 -1
  6. data/app/assets/javascripts/template_invocation.js +14 -1
  7. data/app/assets/stylesheets/job_invocations.css.scss +0 -13
  8. data/app/assets/stylesheets/template_invocation.css.scss +7 -13
  9. data/app/controllers/api/v2/foreign_input_sets_controller.rb +1 -1
  10. data/app/controllers/api/v2/job_invocations_controller.rb +12 -18
  11. data/app/controllers/api/v2/remote_execution_features_controller.rb +38 -0
  12. data/app/controllers/api/v2/template_inputs_controller.rb +1 -0
  13. data/app/controllers/job_invocations_controller.rb +3 -13
  14. data/app/controllers/job_templates_controller.rb +1 -1
  15. data/app/controllers/remote_execution_features_controller.rb +19 -0
  16. data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +1 -22
  17. data/app/helpers/remote_execution_helper.rb +30 -12
  18. data/app/lib/actions/remote_execution/helpers/live_output.rb +2 -2
  19. data/app/lib/actions/remote_execution/run_host_job.rb +7 -4
  20. data/app/lib/actions/remote_execution/run_hosts_job.rb +14 -0
  21. data/app/lib/actions/remote_execution/run_proxy_command.rb +2 -2
  22. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +5 -4
  23. data/app/models/concerns/foreman_remote_execution/subnet_extensions.rb +1 -0
  24. data/app/models/input_template_renderer.rb +14 -3
  25. data/app/models/job_invocation.rb +6 -15
  26. data/app/models/job_invocation_composer.rb +131 -21
  27. data/app/models/job_template.rb +77 -22
  28. data/app/models/job_template_effective_user.rb +0 -2
  29. data/app/models/remote_execution_feature.rb +34 -0
  30. data/app/models/setting/remote_execution.rb +4 -1
  31. data/app/models/targeting.rb +2 -2
  32. data/app/models/template_input.rb +17 -13
  33. data/app/views/api/v2/remote_execution_features/base.json.rabl +3 -0
  34. data/app/views/api/v2/remote_execution_features/index.json.rabl +3 -0
  35. data/app/views/api/v2/remote_execution_features/main.json.rabl +3 -0
  36. data/app/views/api/v2/remote_execution_features/show.json.rabl +3 -0
  37. data/app/views/job_invocations/_form.html.erb +51 -40
  38. data/app/views/job_invocations/_preview_hosts_list.html.erb +1 -1
  39. data/app/views/job_invocations/_tab_hosts.html.erb +6 -3
  40. data/app/views/job_invocations/index.html.erb +11 -11
  41. data/app/views/job_templates/_custom_tabs.html.erb +4 -4
  42. data/app/views/job_templates/index.html.erb +5 -5
  43. data/app/views/overrides/nics/_execution_interface.html.erb +9 -1
  44. data/app/views/remote_execution_features/_form.html.erb +24 -0
  45. data/app/views/remote_execution_features/index.html.erb +21 -0
  46. data/app/views/remote_execution_features/show.html.erb +3 -0
  47. data/app/views/template_inputs/_form.html.erb +1 -0
  48. data/app/views/template_inputs/_invocation_form.html.erb +7 -0
  49. data/app/views/templates/package_action.erb +17 -5
  50. data/app/views/templates/power_action.erb +22 -0
  51. data/app/views/templates/puppet_run_once.erb +1 -1
  52. data/config/routes.rb +4 -0
  53. data/db/migrate/20160113162007_expand_all_template_invocations.rb +1 -1
  54. data/db/migrate/20160118124600_create_remote_execution_features.rb +14 -0
  55. data/db/migrate/20160125155108_make_job_template_name_unique.rb +12 -0
  56. data/db/migrate/20160127134031_add_advanced_to_template_input.rb +11 -0
  57. data/db/migrate/20160127162711_reword_puppet_template_description.rb +9 -0
  58. data/db/migrate/20160203104056_add_concurrency_options_to_job_invocation.rb +6 -0
  59. data/db/seeds.d/70-job_templates.rb +2 -1
  60. data/doc/plugins/div_tag.rb +1 -1
  61. data/doc/plugins/plantuml.rb +1 -1
  62. data/doc/plugins/tags.rb +7 -8
  63. data/doc/plugins/toc.rb +0 -1
  64. data/foreman_remote_execution.gemspec +1 -1
  65. data/lib/foreman_remote_execution/engine.rb +10 -2
  66. data/lib/foreman_remote_execution/version.rb +1 -1
  67. data/locale/action_names.rb +8 -0
  68. data/locale/en/foreman_remote_execution.po +767 -11
  69. data/locale/foreman_remote_execution.pot +1026 -8
  70. data/test/functional/api/v2/foreign_input_sets_controller_test.rb +1 -1
  71. data/test/functional/api/v2/job_invocations_controller_test.rb +0 -9
  72. data/test/functional/api/v2/job_templates_controller_test.rb +11 -11
  73. data/test/functional/api/v2/remote_execution_features_controller_test.rb +35 -0
  74. data/test/functional/api/v2/template_inputs_controller_test.rb +1 -1
  75. data/test/unit/actions/run_hosts_job_test.rb +48 -7
  76. data/test/unit/actions/run_proxy_command_test.rb +1 -1
  77. data/test/unit/concerns/host_extensions_test.rb +11 -0
  78. data/test/unit/input_template_renderer_test.rb +4 -4
  79. data/test/unit/job_invocation_composer_test.rb +52 -2
  80. data/test/unit/job_invocation_test.rb +1 -1
  81. data/test/unit/job_template_test.rb +126 -3
  82. data/test/unit/remote_execution_feature_test.rb +42 -0
  83. data/test/unit/targeting_test.rb +4 -4
  84. metadata +26 -7
  85. data/app/views/job_invocation_task_groups/_job_invocation_task_group.html.erb +0 -31
  86. data/app/views/unattended/snippets/_remote_execution_ssh_keys.erb +0 -18
  87. data/db/seeds.d/80-provision_templates.rb +0 -21
@@ -18,7 +18,7 @@ module Api
18
18
  end
19
19
 
20
20
  test 'should get input set detail' do
21
- get :show, :template_id => @template.to_param, :id => @input_set.to_param
21
+ get :show, :template_id => @template.to_param, :id => @input_set.to_param
22
22
  assert_response :success
23
23
  input_set = ActiveSupport::JSON.decode(@response.body)
24
24
  assert !input_set.empty?
@@ -23,15 +23,6 @@ module Api
23
23
  assert_equal template['job_category'], @invocation.job_category
24
24
  end
25
25
 
26
- test 'should create valid without job_template_id' do
27
- attrs = { :job_category => @template.job_category, :name => 'RandomName', :targeting_type => 'static_query', :search_query => 'foobar'}
28
- post :create, :job_invocation => attrs
29
-
30
- invocation = ActiveSupport::JSON.decode(@response.body)
31
- assert_equal attrs[:job_category], invocation['job_category']
32
- assert_response :success
33
- end
34
-
35
26
  test 'should create valid with job_template_id' do
36
27
  attrs = { :job_category => @template.job_category, :name => 'RandomName', :job_template_id => @template.id,
37
28
  :targeting_type => 'static_query', :search_query => 'foobar'}
@@ -15,7 +15,7 @@ module Api
15
15
  end
16
16
 
17
17
  test 'should get template detail' do
18
- get :show, :id => @template.to_param
18
+ get :show, :id => @template.to_param
19
19
  assert_response :success
20
20
  template = ActiveSupport::JSON.decode(@response.body)
21
21
  assert !template.empty?
@@ -25,7 +25,7 @@ module Api
25
25
  test 'should create valid' do
26
26
  JobTemplate.any_instance.stubs(:valid?).returns(true)
27
27
  valid_attrs = { :template => 'This is a test template', :name => 'RandomName', :provider_type => 'ssh' }
28
- post :create, :job_template => valid_attrs
28
+ post :create, :job_template => valid_attrs
29
29
  template = ActiveSupport::JSON.decode(@response.body)
30
30
  assert template['name'] == 'RandomName'
31
31
  assert_response :success
@@ -38,26 +38,26 @@ module Api
38
38
 
39
39
  test 'should update valid' do
40
40
  JobTemplate.any_instance.stubs(:valid?).returns(true)
41
- put :update, :id => @template.to_param,
42
- :job_template => { :template => 'blah' }
41
+ put :update, :id => @template.to_param,
42
+ :job_template => { :template => 'blah' }
43
43
  assert_response :ok
44
44
  end
45
45
 
46
46
  test 'should not update invalid' do
47
- put :update, :id => @template.to_param,
48
- :job_template => { :name => '' }
47
+ put :update, :id => @template.to_param,
48
+ :job_template => { :name => '' }
49
49
  assert_response :unprocessable_entity
50
50
  end
51
51
 
52
52
  test 'should destroy' do
53
- delete :destroy, :id => @template.to_param
53
+ delete :destroy, :id => @template.to_param
54
54
  assert_response :ok
55
55
  refute JobTemplate.exists?(@template.id)
56
56
  end
57
57
 
58
58
  test 'should clone template' do
59
- post :clone, :id => @template.to_param,
60
- :job_template => {:name => 'MyClone'}
59
+ post :clone, :id => @template.to_param,
60
+ :job_template => {:name => 'MyClone'}
61
61
  assert_response :success
62
62
  template = ActiveSupport::JSON.decode(@response.body)
63
63
  assert_equal(template['name'], 'MyClone')
@@ -65,8 +65,8 @@ module Api
65
65
  end
66
66
 
67
67
  test 'clone name should not be blank' do
68
- post :clone, :id => @template.to_param,
69
- :job_template => {:name => ''}
68
+ post :clone, :id => @template.to_param,
69
+ :job_template => {:name => ''}
70
70
  assert_response :unprocessable_entity
71
71
  end
72
72
  end
@@ -0,0 +1,35 @@
1
+ require 'test_plugin_helper'
2
+
3
+ module Api
4
+ module V2
5
+ class RemoteExecutionFeaturesControllerTest < ActionController::TestCase
6
+ setup do
7
+ @remote_execution_feature = RemoteExecutionFeature.register(:my_awesome_feature, 'My awesome feature',
8
+ :description => 'You will not believe what it does',
9
+ :provided_inputs => ['awesomeness_level'])
10
+ @template = FactoryGirl.create(:job_template, :with_input)
11
+ end
12
+
13
+ test 'should get index' do
14
+ get :index
15
+ remote_execution_features = ActiveSupport::JSON.decode(@response.body)
16
+ refute remote_execution_features.empty?, 'Should respond with input sets'
17
+ assert_response :success
18
+ end
19
+
20
+ test 'should get input set detail' do
21
+ get :show, :id => @remote_execution_feature.to_param
22
+ assert_response :success
23
+ remote_execution_feature = ActiveSupport::JSON.decode(@response.body)
24
+ refute remote_execution_feature.empty?
25
+ assert_equal remote_execution_feature['name'], @remote_execution_feature.name
26
+ end
27
+
28
+ test 'should update valid' do
29
+ put :update, :id => @remote_execution_feature.to_param,
30
+ :job_template_id => @template.id
31
+ assert_response :ok
32
+ end
33
+ end
34
+ end
35
+ end
@@ -16,7 +16,7 @@ module Api
16
16
  end
17
17
 
18
18
  test 'should get input detail' do
19
- get :show, :template_id => @template.to_param, :id => @input.to_param
19
+ get :show, :template_id => @template.to_param, :id => @input.to_param
20
20
  assert_response :success
21
21
  input = ActiveSupport::JSON.decode(@response.body)
22
22
  assert !input.empty?
@@ -22,10 +22,13 @@ module ForemanRemoteExecution
22
22
  end
23
23
  end
24
24
  let(:action) do
25
- action = create_action(Actions::RemoteExecution::RunHostsJob)
26
- action.expects(:action_subject).with(job_invocation)
27
- ForemanTasks::Task::DynflowTask.stubs(:where).returns(mock.tap { |m| m.stubs(:first! => task) })
28
- plan_action(action, job_invocation)
25
+ create_action(Actions::RemoteExecution::RunHostsJob).tap do |action|
26
+ action.expects(:action_subject).with(job_invocation)
27
+ ForemanTasks::Task::DynflowTask.stubs(:where).returns(mock.tap { |m| m.stubs(:first! => task) })
28
+ end
29
+ end
30
+ let(:planned) do
31
+ plan_action action, job_invocation
29
32
  end
30
33
 
31
34
  before do
@@ -35,17 +38,55 @@ module ForemanRemoteExecution
35
38
  end
36
39
 
37
40
  it 'resolves the hosts on targeting in plan phase' do
41
+ planned
38
42
  targeting.hosts.must_include(host)
39
43
  end
40
44
 
41
45
  it 'triggers the RunHostJob actions on the resolved hosts in run phase' do
42
- action.expects(:trigger).with() { |*args| args[0] == Actions::RemoteExecution::RunHostJob }
43
- action.create_sub_plans
46
+ planned.expects(:trigger).with() { |*args| args[0] == Actions::RemoteExecution::RunHostJob }
47
+ planned.create_sub_plans
44
48
  end
45
49
 
46
50
  it 'uses the BindJobInvocation middleware' do
47
- action
51
+ planned
48
52
  job_invocation.task_id.must_equal '123'
49
53
  end
54
+
55
+ describe 'concurrency control' do
56
+ let(:level) { 5 }
57
+ let(:span) { 60 }
58
+
59
+ it 'can be disabled' do
60
+ job_invocation.expects(:concurrency_level)
61
+ job_invocation.expects(:time_span)
62
+ action.expects(:limit_concurrency_level).never
63
+ action.expects(:distribute_over_time).never
64
+ planned
65
+ end
66
+
67
+ it 'can limit concurrency level' do
68
+ job_invocation.expects(:concurrency_level).returns(level).twice
69
+ job_invocation.expects(:time_span)
70
+ action.expects(:limit_concurrency_level).with(level)
71
+ action.expects(:distribute_over_time).never
72
+ planned
73
+ end
74
+
75
+ it 'can distribute tasks over time' do
76
+ job_invocation.expects(:time_span).returns(span).twice
77
+ job_invocation.expects(:concurrency_level)
78
+ action.expects(:distribute_over_time).with(span)
79
+ action.expects(:distribute_over_time).never
80
+ planned
81
+ end
82
+
83
+ it 'can use both' do
84
+ job_invocation.expects(:time_span).returns(span).twice
85
+ action.expects(:distribute_over_time).with(span)
86
+ job_invocation.expects(:concurrency_level).returns(level).twice
87
+ action.expects(:limit_concurrency_level).with(level)
88
+ planned
89
+ end
90
+ end
50
91
  end
51
92
  end
@@ -50,7 +50,7 @@ module ForemanRemoteExecution
50
50
  describe 'when the task is finished' do
51
51
  before do
52
52
  task.state = 'stopped'
53
- task.ended_at = Time.at(timestamp + 1)
53
+ task.ended_at = Time.at(timestamp + 1).utc
54
54
  end
55
55
 
56
56
  describe 'the task finished sucessfully' do
@@ -40,6 +40,17 @@ describe ForemanRemoteExecution::HostExtensions do
40
40
  it 'has ssh keys in the parameters' do
41
41
  host.remote_execution_ssh_keys.must_include sshkey
42
42
  end
43
+
44
+ it 'has ssh keys in the parameters even when no user specified' do
45
+ # this is a case, when using the helper in provisioning templates
46
+ FactoryGirl.create(:smart_proxy, :ssh) do |proxy|
47
+ proxy.organizations << host.organization
48
+ proxy.locations << host.location
49
+ end
50
+ host.interfaces.first.subnet.remote_execution_proxies.clear
51
+ User.current = nil
52
+ host.remote_execution_ssh_keys.must_include sshkey
53
+ end
43
54
  end
44
55
 
45
56
  context 'host has multiple nics' do
@@ -2,14 +2,14 @@ require 'test_plugin_helper'
2
2
 
3
3
  describe InputTemplateRenderer do
4
4
  context 'renderer for simple template without inputs' do
5
- let(:renderer) { InputTemplateRenderer.new(FactoryGirl.build(:job_template, :template => 'id')) }
5
+ let(:renderer) { InputTemplateRenderer.new(FactoryGirl.build(:job_template, :template => 'id <%= preview? %>')) }
6
6
 
7
7
  it 'should render the content' do
8
- renderer.render.must_equal 'id'
8
+ renderer.render.must_equal 'id false'
9
9
  end
10
10
 
11
11
  it 'should render preview' do
12
- renderer.preview.must_equal 'id'
12
+ renderer.preview.must_equal 'id true'
13
13
  end
14
14
  end
15
15
 
@@ -147,7 +147,7 @@ describe InputTemplateRenderer do
147
147
  end
148
148
 
149
149
  let(:package_template) do
150
- FactoryGirl.build(:job_template, :name => 'package action', :template => <<TEMPLATE) do |template|
150
+ FactoryGirl.build(:job_template, :name => 'package action', :template => <<TEMPLATE) do |template|
151
151
  <%= render_template("command action", "command" => "yum -y \#{ input("action") } \#{ input('package') }") -%>
152
152
  TEMPLATE
153
153
  template.template_inputs << FactoryGirl.build(:template_input, :name => 'package', :input_type => 'user')
@@ -210,7 +210,7 @@ describe JobInvocationComposer do
210
210
  { :job_template_id => trying_job_template_1.id.to_s,
211
211
  :job_templates => {
212
212
  trying_job_template_1.id.to_s => {
213
- :input_values => { input1.id.to_s => { :value => 'value1' }, unauthorized_input1.id.to_s => { :value => 'dropped' } }
213
+ :input_values => { input1.id.to_s => { :value => 'value1' }, unauthorized_input1.id.to_s => { :value => 'dropped' } }
214
214
  }
215
215
  }
216
216
  }
@@ -429,6 +429,25 @@ describe JobInvocationComposer do
429
429
  end
430
430
  end
431
431
 
432
+ describe 'concurrency control' do
433
+
434
+ describe 'with concurrency control set' do
435
+ let(:params) do
436
+ { :job_invocation => { :providers => { :ssh => ssh_params }, :concurrency_level => "5", :time_span => "60" } }.with_indifferent_access
437
+ end
438
+
439
+ it 'accepts the concurrency options' do
440
+ composer.job_invocation.concurrency_level.must_equal 5
441
+ composer.job_invocation.time_span.must_equal 60
442
+ end
443
+ end
444
+
445
+ it 'can be disabled' do
446
+ composer.job_invocation.concurrency_level.must_be_nil
447
+ composer.job_invocation.time_span.must_be_nil
448
+ end
449
+ end
450
+
432
451
  describe 'triggering' do
433
452
  let(:params) do
434
453
  { :job_invocation => { :providers => { :ssh => ssh_params } }, :triggering => { :mode => 'future' }}.with_indifferent_access
@@ -481,7 +500,11 @@ describe JobInvocationComposer do
481
500
  let(:params) do
482
501
  {
483
502
  :job_invocation => {
484
- :providers => { :ssh => ssh_params }
503
+ :providers => { :ssh => ssh_params },
504
+ :concurrency_control => {
505
+ :level => 5,
506
+ :time_span => 60
507
+ }
485
508
  },
486
509
  :targeting => {
487
510
  :search_query => "name = #{host.name}",
@@ -514,6 +537,11 @@ describe JobInvocationComposer do
514
537
  new_composer.pattern_template_invocations.size.must_equal existing.pattern_template_invocations.size
515
538
  end
516
539
 
540
+ it 'sets the same concurrency control options' do
541
+ new_composer.job_invocation.concurrency_level.must_equal existing.concurrency_level
542
+ new_composer.job_invocation.time_span.must_equal existing.time_span
543
+ end
544
+
517
545
  end
518
546
  end
519
547
  end
@@ -591,6 +619,28 @@ describe JobInvocationComposer do
591
619
  end
592
620
  end
593
621
 
622
+ context 'with concurrency_control' do
623
+ let(:level) { 5 }
624
+ let(:time_span) { 60 }
625
+ let(:params) do
626
+ { :job_category => trying_job_template_1.job_category,
627
+ :job_template_id => trying_job_template_1.id,
628
+ :concurrency_control => {
629
+ :concurrency_level => level,
630
+ :time_span => time_span
631
+ },
632
+ :targeting_type => 'static_query',
633
+ :search_query => 'some hosts',
634
+ :inputs => {input1.name => 'some_value'}}
635
+ end
636
+
637
+ it 'sets the concurrency level and time span based on the input' do
638
+ assert composer.save!
639
+ composer.job_invocation.time_span.must_equal time_span
640
+ composer.job_invocation.concurrency_level.must_equal level
641
+ end
642
+ end
643
+
594
644
  context 'with invalid targeting' do
595
645
  let(:params) do
596
646
  { :job_category => trying_job_template_1.job_category,
@@ -92,7 +92,7 @@ describe JobInvocation do
92
92
  context 'future execution' do
93
93
  it 'can report host count' do
94
94
  job_invocation.total_hosts_count.must_equal 'N/A'
95
- job_invocation.targeting.expects(:resolved_at).returns(Time.now)
95
+ job_invocation.targeting.expects(:resolved_at).returns(Time.now.getlocal)
96
96
  job_invocation.total_hosts_count.must_equal 0
97
97
  end
98
98
 
@@ -10,6 +10,12 @@ describe JobTemplate do
10
10
  end
11
11
  end
12
12
 
13
+ it 'has a unique name' do
14
+ template1 = FactoryGirl.create(:job_template)
15
+ template2 = FactoryGirl.build(:job_template, :name => template1.name)
16
+ refute template2.valid?
17
+ end
18
+
13
19
  it 'needs a job_category' do
14
20
  refute job_template.valid?
15
21
  end
@@ -45,7 +51,7 @@ describe JobTemplate do
45
51
 
46
52
  it 'generates the description_format if not set or blank and has inputs' do
47
53
  input_name = template.template_inputs.first.name
48
- expected_result = %Q(%{job_category} with inputs #{input_name}="%{#{input_name}}")
54
+ expected_result = %(%{job_category} with inputs #{input_name}="%{#{input_name}}")
49
55
  template.generate_description_format.must_equal expected_result
50
56
  template.description_format = ''
51
57
  template.generate_description_format.must_equal expected_result
@@ -66,7 +72,7 @@ describe JobTemplate do
66
72
  end
67
73
  end
68
74
 
69
- context 'importing a template' do
75
+ context 'importing a new template' do
70
76
  let(:template) do
71
77
  template = <<-END_TEMPLATE
72
78
  <%#
@@ -78,12 +84,32 @@ describe JobTemplate do
78
84
  - name: service_name
79
85
  input_type: user
80
86
  required: true
87
+ - name: verbose
88
+ input_type: user
81
89
  %>
82
90
 
83
91
  service <%= input("service_name") %> restart
84
92
  END_TEMPLATE
85
93
 
86
- JobTemplate.import(template, :default => true)
94
+ JobTemplate.import!(template, :default => true)
95
+ end
96
+
97
+ let(:template_with_input_sets) do
98
+ template_with_input_sets = <<-END_TEMPLATE
99
+ <%#
100
+ kind: job_template
101
+ name: Service Restart - Custom
102
+ job_category: Service Restart
103
+ provider_type: SSH
104
+ foreign_input_sets:
105
+ - template: #{template.name}
106
+ exclude: verbose
107
+ %>
108
+
109
+ service <%= input("service_name") %> restart
110
+ END_TEMPLATE
111
+
112
+ JobTemplate.import!(template_with_input_sets, :default => true)
87
113
  end
88
114
 
89
115
  it 'sets the name' do
@@ -98,11 +124,108 @@ describe JobTemplate do
98
124
  template.template_inputs.first.name.must_equal 'service_name'
99
125
  end
100
126
 
127
+ it 'imports input sets' do
128
+ template_with_input_sets.foreign_input_sets.first.target_template.must_equal template
129
+ template_with_input_sets.template_inputs_with_foreign.map(&:name).must_equal ['service_name']
130
+ end
131
+
101
132
  it 'sets additional options' do
102
133
  template.default.must_equal true
103
134
  end
104
135
  end
105
136
 
137
+ context 'importing an existing template' do
138
+ let(:included) do
139
+ template = <<-END_TEMPLATE
140
+ <%#
141
+ kind: job_template
142
+ name: Banner
143
+ job_category: Commands
144
+ provider_type: SSH
145
+ template_inputs:
146
+ - name: banner_message
147
+ input_type: user
148
+ required: true
149
+ %>
150
+
151
+ echo input(:banner_message)
152
+ END_TEMPLATE
153
+
154
+ JobTemplate.import!(template, :default => true)
155
+ end
156
+
157
+ let(:existing) do
158
+ template = <<-END_TEMPLATE
159
+ <%#
160
+ kind: job_template
161
+ name: Ping a Thing
162
+ job_category: Commands
163
+ provider_type: SSH
164
+ template_inputs:
165
+ - name: hostname
166
+ input_type: user
167
+ options: "www.google.com"
168
+ required: true
169
+ foreign_input_sets:
170
+ - template: #{included.name}
171
+ %>
172
+
173
+ ping -c 5 <%= input("hostname") %>
174
+ END_TEMPLATE
175
+
176
+ JobTemplate.import!(template, :default => true)
177
+ end
178
+
179
+ let(:updated) do
180
+ <<-END_TEMPLATE
181
+ <%#
182
+ kind: job_template
183
+ name: Ping a Thing
184
+ job_category: Commands
185
+ provider_type: SSH
186
+ template_inputs:
187
+ - name: hostname
188
+ input_type: user
189
+ options: 'www.redhat.com'
190
+ required: true
191
+ - name: count
192
+ input_type: user
193
+ required: true
194
+ foreign_input_sets:
195
+ - template: #{included.name}
196
+ exclude: banner_message
197
+ %>
198
+
199
+ ping -c <%= input('count') %> <%= input('hostname') %>
200
+ END_TEMPLATE
201
+ end
202
+
203
+ it 'will not overwrite by default' do
204
+ existing
205
+ refute JobTemplate.import!(updated)
206
+ end
207
+
208
+ let(:synced_template) do
209
+ existing
210
+ JobTemplate.import!(updated, :update => true)
211
+ existing.reload
212
+ end
213
+
214
+ it 'syncs inputs' do
215
+ hostname = synced_template.template_inputs.find { |input| input.name == 'hostname' }
216
+ hostname.options.must_equal 'www.redhat.com'
217
+ end
218
+
219
+ it 'syncs content' do
220
+ synced_template.template.must_match(/\s+ping -c <%= input\('count'\) %> <%= input\('hostname'\) %>\s+/m)
221
+ end
222
+
223
+ it 'syncs input sets' do
224
+ synced_template.foreign_input_sets.first.target_template.must_equal included
225
+ synced_template.template_inputs_with_foreign.map(&:name).must_equal ["hostname", "count"]
226
+ end
227
+ end
228
+
106
229
  context 'there is existing template invocation of a job template' do
107
230
  let(:job_invocation) { FactoryGirl.create(:job_invocation, :with_template) }
108
231
  let(:job_template) { job_invocation.pattern_template_invocations.first.template }