foreman_remote_execution 0.2.3 → 0.3.0

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.
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 }