foreman_remote_execution 0.0.1

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 (83) hide show
  1. checksums.yaml +15 -0
  2. data/LICENSE +619 -0
  3. data/README.md +54 -0
  4. data/Rakefile +47 -0
  5. data/app/assets/javascripts/template_input.js +9 -0
  6. data/app/assets/javascripts/template_invocation.js +32 -0
  7. data/app/assets/stylesheets/job_invocations.css.scss +35 -0
  8. data/app/assets/stylesheets/template_invocation.css.scss +16 -0
  9. data/app/controllers/api/v2/job_templates_controller.rb +108 -0
  10. data/app/controllers/job_invocations_controller.rb +35 -0
  11. data/app/controllers/job_templates_controller.rb +35 -0
  12. data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +40 -0
  13. data/app/helpers/remote_execution_helper.rb +88 -0
  14. data/app/lib/actions/remote_execution/run_host_job.rb +93 -0
  15. data/app/lib/actions/remote_execution/run_hosts_job.rb +35 -0
  16. data/app/lib/actions/remote_execution/run_proxy_command.rb +34 -0
  17. data/app/models/concerns/foreman_remote_execution/bookmark_extensions.rb +9 -0
  18. data/app/models/concerns/foreman_remote_execution/foreman_tasks_task_extensions.rb +9 -0
  19. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +19 -0
  20. data/app/models/concerns/foreman_remote_execution/template_extensions.rb +20 -0
  21. data/app/models/concerns/foreman_remote_execution/template_relations.rb +10 -0
  22. data/app/models/concerns/foreman_remote_execution/user_extensions.rb +9 -0
  23. data/app/models/input_template_renderer.rb +42 -0
  24. data/app/models/job_invocation.rb +21 -0
  25. data/app/models/job_invocation_composer.rb +210 -0
  26. data/app/models/job_template.rb +52 -0
  27. data/app/models/remote_execution_provider.rb +17 -0
  28. data/app/models/setting/remote_execution.rb +19 -0
  29. data/app/models/ssh_execution_provider.rb +2 -0
  30. data/app/models/targeting.rb +56 -0
  31. data/app/models/targeting_host.rb +9 -0
  32. data/app/models/template_input.rb +154 -0
  33. data/app/models/template_invocation.rb +13 -0
  34. data/app/models/template_invocation_input_value.rb +8 -0
  35. data/app/views/api/v2/job_templates/base.json.rabl +3 -0
  36. data/app/views/api/v2/job_templates/create.json.rabl +3 -0
  37. data/app/views/api/v2/job_templates/index.json.rabl +3 -0
  38. data/app/views/api/v2/job_templates/main.json.rabl +5 -0
  39. data/app/views/api/v2/job_templates/show.json.rabl +9 -0
  40. data/app/views/job_invocations/_form.html.erb +67 -0
  41. data/app/views/job_invocations/_tab_hosts.html.erb +33 -0
  42. data/app/views/job_invocations/_tab_overview.html.erb +41 -0
  43. data/app/views/job_invocations/index.html.erb +30 -0
  44. data/app/views/job_invocations/new.html.erb +8 -0
  45. data/app/views/job_invocations/refresh.js.erb +1 -0
  46. data/app/views/job_invocations/show.html.erb +21 -0
  47. data/app/views/job_templates/_custom_tab_headers.html.erb +2 -0
  48. data/app/views/job_templates/_custom_tabs.html.erb +28 -0
  49. data/app/views/job_templates/auto_complete_job_name.json.erb +3 -0
  50. data/app/views/job_templates/edit.html.erb +6 -0
  51. data/app/views/job_templates/index.html.erb +33 -0
  52. data/app/views/job_templates/new.html.erb +6 -0
  53. data/app/views/template_inputs/_form.html.erb +22 -0
  54. data/config/routes.rb +35 -0
  55. data/db/migrate/20150612121541_add_job_template_to_template.rb +6 -0
  56. data/db/migrate/20150616080015_create_template_input.rb +19 -0
  57. data/db/migrate/20150708133241_add_targeting.rb +25 -0
  58. data/db/migrate/20150708133242_add_invocation.rb +11 -0
  59. data/db/migrate/20150708133305_add_template_invocation.rb +22 -0
  60. data/db/migrate/20150812110800_add_resolved_at_to_targeting.rb +5 -0
  61. data/db/migrate/20150812145900_add_last_task_id_to_job_invocation.rb +6 -0
  62. data/db/seeds.d/60-ssh_proxy_feature.rb +2 -0
  63. data/lib/foreman_remote_execution/engine.rb +119 -0
  64. data/lib/foreman_remote_execution/version.rb +3 -0
  65. data/lib/foreman_remote_execution.rb +6 -0
  66. data/lib/tasks/foreman_remote_execution_tasks.rake +49 -0
  67. data/locale/Makefile +62 -0
  68. data/locale/en/foreman_remote_execution.po +19 -0
  69. data/locale/foreman_remote_execution.pot +19 -0
  70. data/locale/gemspec.rb +2 -0
  71. data/test/factories/foreman_remote_execution_factories.rb +48 -0
  72. data/test/functional/api/v2/job_templates_controller_test.rb +74 -0
  73. data/test/test_plugin_helper.rb +8 -0
  74. data/test/unit/actions/run_hosts_job_test.rb +40 -0
  75. data/test/unit/actions/run_proxy_command_test.rb +30 -0
  76. data/test/unit/input_template_renderer_test.rb +366 -0
  77. data/test/unit/job_invocation_composer_test.rb +415 -0
  78. data/test/unit/job_invocation_test.rb +31 -0
  79. data/test/unit/job_template_test.rb +5 -0
  80. data/test/unit/remote_execution_provider_test.rb +51 -0
  81. data/test/unit/targeting_test.rb +107 -0
  82. data/test/unit/template_input_test.rb +25 -0
  83. metadata +195 -0
@@ -0,0 +1,9 @@
1
+ module ForemanRemoteExecution
2
+ module ForemanTasksTaskExtensions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :job_invocations, :dependent => :nullify, :foreign_key => 'last_task_id'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ module ForemanRemoteExecution
2
+ module HostExtensions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :targeting_hosts, :dependent => :destroy, :foreign_key => 'host_id'
7
+ end
8
+
9
+ # create or overwrite instance methods...
10
+ def instance_method_name
11
+ end
12
+
13
+ module ClassMethods
14
+ # create or overwrite class methods...
15
+ def class_method_name
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ module ForemanRemoteExecution
2
+ module TemplateExtensions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ accepts_nested_attributes_for :template_inputs, :allow_destroy => true
7
+ attr_accessible :template_inputs_attributes
8
+ end
9
+
10
+ # create or overwrite instance methods...
11
+ # def instance_method_name
12
+ # end
13
+
14
+ # module ClassMethods
15
+ # # create or overwrite class methods...
16
+ # def class_method_name
17
+ # end
18
+ # end
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ module ForemanRemoteExecution
2
+ module TemplateRelations
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ # autosave => true is required so the changes of inputs are saved even if template was not changed
7
+ has_many :template_inputs, :dependent => :destroy, :foreign_key => 'template_id', :autosave => true
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ module ForemanRemoteExecution
2
+ module UserExtensions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :targetings, :dependent => :nullify
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,42 @@
1
+ class InputTemplateRenderer
2
+ class UndefinedInput < ::Foreman::Exception
3
+ end
4
+
5
+ include UnattendedHelper
6
+
7
+ attr_accessor :template, :host, :invocation, :error_message
8
+
9
+ # takes template object that should be rendered
10
+ # host and template invocation arguments are optional
11
+ # so we can render values based on parameters, facts or user inputs
12
+ def initialize(template, host = nil, invocation = nil)
13
+ @host = host
14
+ @invocation = invocation
15
+ @template = template
16
+ end
17
+
18
+ def render
19
+ render_safe(@template.template, [ :input ], :host => @host)
20
+ rescue => e
21
+ self.error_message ||= _('error during rendering: %s') % e.message
22
+ Rails.logger.debug e.to_s + "\n" + e.backtrace.join("\n")
23
+ return false
24
+ end
25
+
26
+ def preview
27
+ @preview = true
28
+ output = render
29
+ @preview = false
30
+ output
31
+ end
32
+
33
+ def input(name)
34
+ input = @template.template_inputs.where(:name => name.to_s).first || @template.template_inputs.detect { |i| i.name == name.to_s }
35
+ if input
36
+ @preview ? input.preview(self) : input.value(self)
37
+ else
38
+ self.error_message = _('input macro with name \'%s\' used, but no input with such name defined for this template') % name
39
+ raise UndefinedInput, "Rendering failed, no input with name #{name} for input macro found"
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,21 @@
1
+ class JobInvocation < ActiveRecord::Base
2
+ include Authorizable
3
+
4
+ belongs_to :targeting, :dependent => :destroy
5
+ has_many :template_invocations, :inverse_of => :job_invocation, :dependent => :destroy
6
+
7
+ validates :targeting, :presence => true
8
+ validates :job_name, :presence => true
9
+
10
+ delegate :bookmark, :to => :targeting, :allow_nil => true
11
+
12
+ include ForemanTasks::Concerns::ActionSubject
13
+
14
+ belongs_to :last_task, :class_name => 'ForemanTasks::Task'
15
+
16
+ scoped_search :on => [:job_name], :complete_value => true
17
+
18
+ def to_action_input
19
+ { :id => id, :name => job_name }
20
+ end
21
+ end
@@ -0,0 +1,210 @@
1
+ class JobInvocationComposer
2
+ attr_accessor :params, :job_invocation, :host_ids, :search_query
3
+ attr_reader :job_template_ids
4
+ delegate :job_name, :targeting, :to => :job_invocation
5
+
6
+ def initialize(job_invocation, params)
7
+ @job_invocation = job_invocation
8
+ @params = params
9
+
10
+ @host_ids = validate_host_ids(params[:host_ids])
11
+ @search_query = targeting_base[:search_query]
12
+
13
+ job_invocation.job_name = validate_job_name(job_invocation_base[:job_name])
14
+ job_invocation.job_name ||= available_job_names.first if job_invocation.new_record?
15
+ job_invocation.targeting = build_targeting
16
+
17
+ @job_template_ids = validate_job_template_ids(job_templates_base.keys.compact)
18
+ end
19
+
20
+ def valid?
21
+ targeting.valid? & job_invocation.valid? & !template_invocations.map(&:valid?).include?(false)
22
+ end
23
+
24
+ def save
25
+ valid? && job_invocation.save
26
+ end
27
+
28
+ def available_templates
29
+ JobTemplate.authorized(:view_job_templates)
30
+ end
31
+
32
+ def available_templates_for(job_name)
33
+ available_templates.where(:job_name => job_name)
34
+ end
35
+
36
+ def available_job_names
37
+ available_templates.reorder(:job_name).group(:job_name).pluck(:job_name)
38
+ end
39
+
40
+ def available_provider_types
41
+ available_templates_for(job_name).reorder(:provider_type).group(:provider_type).pluck(:provider_type)
42
+ end
43
+
44
+ def available_template_inputs
45
+ TemplateInput.where(:template_id => job_template_ids.nil? ? available_templates_for(job_name).map(&:id) : job_template_ids)
46
+ end
47
+
48
+ def needs_provider_type_selection?
49
+ available_provider_types.size > 1
50
+ end
51
+
52
+ def only_one_template_available?
53
+ !needs_provider_type_selection? && templates_for_provider(available_provider_types.first).size == 1
54
+ end
55
+
56
+ def displayed_provider_types
57
+ # TODO available_provider_types based on targets
58
+ available_provider_types
59
+ end
60
+
61
+ def templates_for_provider(provider_type)
62
+ available_templates_for(job_name).select { |t| t.provider_type == provider_type }
63
+ end
64
+
65
+ def selected_job_templates
66
+ available_templates_for(job_name).where(:id => job_template_ids)
67
+ end
68
+
69
+ def preselect_disabled_for_provider(provider_type)
70
+ (templates_for_provider(provider_type) & selected_job_templates).empty?
71
+ end
72
+
73
+ def template_invocations
74
+ if job_invocation.new_record?
75
+ @template_invocations ||= build_template_invocations
76
+ else
77
+ job_invocation.template_invocations
78
+ # TODO update if base present? that would solve updating
79
+ end
80
+ end
81
+
82
+ def displayed_search_query
83
+ if @search_query.present?
84
+ @search_query
85
+ elsif host_ids.present?
86
+ targeting.build_query_from_hosts(host_ids)
87
+ elsif targeting.bookmark_id
88
+ if (bookmark = available_bookmarks.where(:id => targeting.bookmark_id).first)
89
+ bookmark.query
90
+ else
91
+ ''
92
+ end
93
+ else
94
+ ''
95
+ end
96
+ end
97
+
98
+ def available_bookmarks
99
+ Bookmark.authorized(:view_bookmarks).my_bookmarks
100
+ end
101
+
102
+ def targeted_hosts_count
103
+ Host.authorized(:view_hosts, Host).search_for(displayed_search_query).count
104
+ rescue
105
+ 0
106
+ end
107
+
108
+ def template_invocation_input_value_for(input)
109
+ invocations = template_invocations
110
+ default = TemplateInvocationInputValue.new
111
+ if (invocation = invocations.detect { |i| i.template_id == input.template_id })
112
+ invocation.input_values.detect { |iv| iv.template_input_id == input.id } || default
113
+ else
114
+ default
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ def targeting_base
121
+ @params.fetch(:targeting, {})
122
+ end
123
+
124
+ def job_invocation_base
125
+ @params.fetch(:job_invocation, {})
126
+ end
127
+
128
+ def input_values_base
129
+ @params.fetch(:input_values, [])
130
+ end
131
+
132
+ def providers_base
133
+ job_invocation_base.fetch(:providers, {})
134
+ end
135
+
136
+ # parses params to get job templates in form of id => attributes for selected job templates, e.g.
137
+ # {
138
+ # "459" => {},
139
+ # "454" => {
140
+ # "input_values" => {
141
+ # "2" => {
142
+ # "value" => ""
143
+ # },
144
+ # "5" => {
145
+ # "value" => ""
146
+ # }
147
+ # }
148
+ # }
149
+ # }
150
+ def job_templates_base
151
+ Hash[providers_base.values.map { |jt| [jt['job_template_id'], (jt['job_templates'] || {})[jt['job_template_id']] || {}] }]
152
+ end
153
+
154
+ # builds input values for a given templates id based on params
155
+ # omits inputs that belongs to unavailable templates
156
+ def build_input_values_for(job_template_id)
157
+ job_templates_base[job_template_id.to_s].fetch('input_values', {}).map do |input_id, attributes|
158
+ input = available_template_inputs.find_by_id(input_id)
159
+ input ? input.template_invocation_input_values.build(attributes) : nil
160
+ end.compact
161
+ end
162
+
163
+ def build_targeting
164
+ # if bookmark was used we compare it to search query,
165
+ # when it's the same, we delete the query since it is used from bookmark
166
+ # when no bookmark is set we store the query
167
+ bookmark_id = targeting_base[:bookmark_id]
168
+ bookmark = available_bookmarks.where(:id => bookmark_id).first
169
+ query = targeting_base[:search_query]
170
+ if bookmark.present? && query.present?
171
+ if query.strip == bookmark.query.strip
172
+ query = nil
173
+ else
174
+ bookmark_id = nil
175
+ end
176
+ elsif query.present?
177
+ query = targeting_base[:search_query]
178
+ bookmark_id = nil
179
+ end
180
+
181
+ Targeting.new(
182
+ :user => User.current,
183
+ :bookmark_id => bookmark_id,
184
+ :targeting_type => targeting_base[:targeting_type],
185
+ :search_query => query
186
+ )
187
+ end
188
+
189
+ def build_template_invocations
190
+ job_template_ids.map do |job_template_id|
191
+ template_invocation = job_invocation.template_invocations.build(:template_id => job_template_id)
192
+ template_invocation.input_values = build_input_values_for(job_template_id)
193
+ template_invocation
194
+ end
195
+ end
196
+
197
+ # returns nil if user can't see any job template with such name
198
+ # existing job_name string otherwise
199
+ def validate_job_name(name)
200
+ available_job_names.include?(name) ? name : nil
201
+ end
202
+
203
+ def validate_job_template_ids(ids)
204
+ available_templates_for(job_name).where(:id => ids).pluck(:id)
205
+ end
206
+
207
+ def validate_host_ids(ids)
208
+ Host.authorized(:view_hosts, Host).where(:id => ids).pluck(:id)
209
+ end
210
+ end
@@ -0,0 +1,52 @@
1
+ class JobTemplate < ::Template
2
+ attr_accessible :job_name, :provider_type
3
+
4
+ include Authorizable
5
+ extend FriendlyId
6
+ friendly_id :name
7
+ include Parameterizable::ByIdName
8
+
9
+ audited :allow_mass_assignment => true
10
+ has_many :audits, :as => :auditable, :class_name => Audited.audit_class.name
11
+
12
+ # these can't be shared in parent class, scoped search can't handle STI properly
13
+ # tested with scoped_search 3.2.0
14
+ include Taxonomix
15
+ scoped_search :on => :name, :complete_value => true, :default_order => true
16
+ scoped_search :on => :job_name, :complete_value => true
17
+ scoped_search :on => :locked, :complete_value => {:true => true, :false => false}
18
+ scoped_search :on => :snippet, :complete_value => {:true => true, :false => false}
19
+ scoped_search :on => :provider_type, :complete_value => true
20
+ scoped_search :on => :template
21
+
22
+ # with proc support, default_scope can no longer be chained
23
+ # include all default scoping here
24
+ default_scope lambda {
25
+ with_taxonomy_scope do
26
+ order("#{Template.table_name}.name")
27
+ end
28
+ }
29
+
30
+ validates :provider_type, :presence => true
31
+ validate :provider_type_whitelist
32
+
33
+ # we have to override the base_class because polymorphic associations does not detect it correctly, more details at
34
+ # http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_many#1010-Polymorphic-has-many-within-inherited-class-gotcha
35
+ def self.base_class
36
+ self
37
+ end
38
+ self.table_name = 'templates'
39
+
40
+ # Override method in Taxonomix as Template is not used attached to a Host,
41
+ # and matching a Host does not prevent removing a template from its taxonomy.
42
+ def used_taxonomy_ids(type)
43
+ []
44
+ end
45
+
46
+ private
47
+
48
+ # we can't use standard validator, .provider_names output can change but the validator does not reflect it
49
+ def provider_type_whitelist
50
+ errors.add :provider_type, :uniq unless RemoteExecutionProvider.provider_names.include?(self.provider_type)
51
+ end
52
+ end
@@ -0,0 +1,17 @@
1
+ class RemoteExecutionProvider
2
+ def self.provider_for(type)
3
+ providers[type.to_s] || providers[:Ssh]
4
+ end
5
+
6
+ def self.providers
7
+ @providers ||= { :Ssh => N_(SSHExecutionProvider) }.with_indifferent_access
8
+ end
9
+
10
+ def self.register(key, klass)
11
+ providers[key.to_sym] = klass
12
+ end
13
+
14
+ def self.provider_names
15
+ providers.keys.map(&:to_s)
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ class Setting::RemoteExecution < Setting
2
+
3
+ def self.load_defaults
4
+ # Check the table exists
5
+ return unless super
6
+
7
+ self.transaction do
8
+ [
9
+ self.set('remote_execution_global_proxy',
10
+ N_("Allows searching for remote execution proxy outside of the proxies assigned to the host: useful when missing the host proxies associations"),
11
+ false),
12
+ ].each { |s| self.create! s.update(:category => "Setting::RemoteExecution") }
13
+ end
14
+
15
+ true
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,2 @@
1
+ class SSHExecutionProvider < RemoteExecutionProvider
2
+ end
@@ -0,0 +1,56 @@
1
+ class Targeting < ActiveRecord::Base
2
+
3
+ STATIC_TYPE = 'static_query'
4
+ DYNAMIC_TYPE = 'dynamic_query'
5
+ TYPES = { STATIC_TYPE => N_('Static Query'), DYNAMIC_TYPE => N_('Dynamic Query') }
6
+
7
+ belongs_to :user
8
+ belongs_to :bookmark
9
+
10
+ has_many :targeting_hosts, :dependent => :destroy
11
+ has_many :hosts, :through => :targeting_hosts
12
+ has_one :job_invocation
13
+ has_many :template_invocations, :through => :job_invocation
14
+
15
+ validates :targeting_type, :presence => true, :inclusion => Targeting::TYPES.keys
16
+ validate :bookmark_or_query_is_present
17
+
18
+ attr_accessible :targeting_type, :bookmark_id, :user, :search_query
19
+
20
+ before_create :assign_search_query, :if => :static?
21
+
22
+ def resolve_hosts!
23
+ raise ::Foreman::Exception, _('Cannot resolve hosts without a user') if user.nil?
24
+ raise ::Foreman::Exception, _('Cannot resolve hosts without a bookmark or search query') if bookmark.nil? && search_query.blank?
25
+
26
+ self.search_query = bookmark.query if dynamic? && bookmark.present?
27
+ self.touch(:resolved_at)
28
+ self.save!
29
+ self.hosts = User.as(user.login) { Host.authorized('edit_hosts', Host).search_for(search_query) }
30
+ end
31
+
32
+ def dynamic?
33
+ targeting_type == DYNAMIC_TYPE
34
+ end
35
+
36
+ def static?
37
+ targeting_type == STATIC_TYPE
38
+ end
39
+
40
+ def build_query_from_hosts(ids)
41
+ hosts = Host.where(:id => ids).all.group_by(&:id)
42
+ ids.map { |id| "name = #{hosts[id].first.name}" }.join(' or ')
43
+ end
44
+
45
+ private
46
+
47
+ def bookmark_or_query_is_present
48
+ if bookmark.nil? && search_query.nil?
49
+ errors.add :base, _('Bookmark or search query can\'t be nil')
50
+ end
51
+ end
52
+
53
+ def assign_search_query
54
+ self.search_query = bookmark.query if static? && bookmark
55
+ end
56
+ end
@@ -0,0 +1,9 @@
1
+ class TargetingHost < ActiveRecord::Base
2
+
3
+ belongs_to :targeting
4
+ belongs_to_host
5
+
6
+ validates :targeting, :presence => true
7
+ validates :host, :presence => true
8
+
9
+ end
@@ -0,0 +1,154 @@
1
+ class TemplateInput < ActiveRecord::Base
2
+ class ValueNotReady < ::Foreman::Exception
3
+ end
4
+
5
+ TYPES = { :user => N_('User input'), :fact => N_('Fact value'), :variable => N_('Variable'),
6
+ :puppet_parameter => N_('Puppet parameter') }.with_indifferent_access
7
+
8
+ attr_accessible :name, :required, :input_type, :fact_name, :variable_name,
9
+ :puppet_class_name, :puppet_parameter_name, :description, :job_template_id
10
+
11
+ belongs_to :template
12
+ has_many :template_invocation_input_values, :dependent => :destroy
13
+
14
+ validates :name, :presence => true, :uniqueness => { :scope => 'template_id' }
15
+ validates :input_type, :presence => true, :inclusion => TemplateInput::TYPES.keys
16
+
17
+ validates :fact_name, :presence => { :if => :fact_template_input? }
18
+ validates :variable_name, :presence => { :if => :variable_template_input? }
19
+ validates :puppet_parameter_name, :puppet_class_name, :presence => { :if => :puppet_parameter_template_input? }
20
+
21
+ def user_template_input?
22
+ input_type == 'user'
23
+ end
24
+
25
+ def fact_template_input?
26
+ input_type == 'fact'
27
+ end
28
+
29
+ def variable_template_input?
30
+ input_type == 'variable'
31
+ end
32
+
33
+ def puppet_parameter_template_input?
34
+ input_type == 'puppet_parameter'
35
+ end
36
+
37
+ def preview(renderer)
38
+ get_resolver(renderer).preview
39
+ end
40
+
41
+ def value(renderer)
42
+ get_resolver(renderer).value
43
+ end
44
+
45
+ private
46
+
47
+ def get_resolver(renderer)
48
+ resolver_class = case input_type
49
+ when 'user'
50
+ UserInputResolver
51
+ when 'fact'
52
+ FactInputResolver
53
+ when 'variable'
54
+ VariableInputResolver
55
+ when 'puppet_parameter'
56
+ PuppetParameterInputResolver
57
+ else
58
+ raise "unknown template input type #{input_type.inspect}"
59
+ end
60
+ resolver_class.new(self, renderer)
61
+ end
62
+
63
+ class InputResolver
64
+ def initialize(input, renderer)
65
+ @input = input
66
+ @renderer = renderer
67
+ end
68
+
69
+ def preview
70
+ ready? ? resolved_value : preview_value
71
+ end
72
+
73
+ def value
74
+ ready? ? resolved_value : raise(ValueNotReady, "Input '#{@input.name}' is not ready for rendering")
75
+ end
76
+
77
+ def preview_value
78
+ "$#{@input.input_type.upcase}_INPUT[#{@input.name}]"
79
+ end
80
+
81
+ # should be defined in descendants
82
+ def ready?
83
+ raise NotImplementedError
84
+ end
85
+
86
+ # should be defined in descendants
87
+ def resolved_value
88
+ raise NotImplementedError
89
+ end
90
+ end
91
+
92
+ class UserInputResolver < InputResolver
93
+ def ready?
94
+ !input_value.nil?
95
+ end
96
+
97
+ def resolved_value
98
+ input_value.value
99
+ end
100
+
101
+ private
102
+
103
+ def input_value
104
+ return unless @renderer.invocation
105
+ @renderer.invocation.input_values.find { |value| value.template_input_name == @input.name }
106
+ end
107
+ end
108
+
109
+ class FactInputResolver < InputResolver
110
+ # fact might not be present if it hasn't been uploaded yet, there's typo in name
111
+ def ready?
112
+ @renderer.host && get_fact.present?
113
+ end
114
+
115
+ def resolved_value
116
+ get_fact.value
117
+ end
118
+
119
+ private
120
+
121
+ def get_fact
122
+ @fact ||= @renderer.host.fact_values.includes(:fact_name).where(:'fact_names.name' => @input.fact_name).first
123
+ end
124
+ end
125
+
126
+ class VariableInputResolver < InputResolver
127
+ def ready?
128
+ @renderer.host && @renderer.host.params.key?(@input.variable_name)
129
+ end
130
+
131
+ def resolved_value
132
+ @renderer.host.params[@input.variable_name]
133
+ end
134
+ end
135
+
136
+ class PuppetParameterInputResolver < InputResolver
137
+ def ready?
138
+ @renderer.host &&
139
+ get_enc.key?(@input.puppet_class_name) &&
140
+ get_enc[@input.puppet_class_name].is_a?(Hash) &&
141
+ get_enc[@input.puppet_class_name].key?(@input.puppet_parameter_name)
142
+ end
143
+
144
+ def resolved_value
145
+ get_enc[@input.puppet_class_name][@input.puppet_parameter_name]
146
+ end
147
+
148
+ private
149
+
150
+ def get_enc
151
+ @enc ||= Classification::ClassParam.new(:host => @renderer.host).enc
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,13 @@
1
+ class TemplateInvocation < ActiveRecord::Base
2
+
3
+ belongs_to :template, :class_name => 'JobTemplate', :foreign_key => 'template_id'
4
+ belongs_to :job_invocation, :inverse_of => :template_invocations
5
+ has_many :input_values, :class_name => 'TemplateInvocationInputValue', :dependent => :destroy
6
+ has_one :targeting, :through => :job_invocation
7
+
8
+ include ForemanTasks::Concerns::ActionSubject
9
+
10
+ def to_action_input
11
+ { :id => id, :name => template.name }
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ class TemplateInvocationInputValue < ActiveRecord::Base
2
+
3
+ belongs_to :template_invocation
4
+ belongs_to :template_input
5
+
6
+ validates :value, :presence => true, :if => proc { |v| v.template_input.required? }
7
+
8
+ end
@@ -0,0 +1,3 @@
1
+ object @job_template
2
+
3
+ attributes :id, :name, :job_name, :provider_type