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.
- checksums.yaml +15 -0
- data/LICENSE +619 -0
- data/README.md +54 -0
- data/Rakefile +47 -0
- data/app/assets/javascripts/template_input.js +9 -0
- data/app/assets/javascripts/template_invocation.js +32 -0
- data/app/assets/stylesheets/job_invocations.css.scss +35 -0
- data/app/assets/stylesheets/template_invocation.css.scss +16 -0
- data/app/controllers/api/v2/job_templates_controller.rb +108 -0
- data/app/controllers/job_invocations_controller.rb +35 -0
- data/app/controllers/job_templates_controller.rb +35 -0
- data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +40 -0
- data/app/helpers/remote_execution_helper.rb +88 -0
- data/app/lib/actions/remote_execution/run_host_job.rb +93 -0
- data/app/lib/actions/remote_execution/run_hosts_job.rb +35 -0
- data/app/lib/actions/remote_execution/run_proxy_command.rb +34 -0
- data/app/models/concerns/foreman_remote_execution/bookmark_extensions.rb +9 -0
- data/app/models/concerns/foreman_remote_execution/foreman_tasks_task_extensions.rb +9 -0
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +19 -0
- data/app/models/concerns/foreman_remote_execution/template_extensions.rb +20 -0
- data/app/models/concerns/foreman_remote_execution/template_relations.rb +10 -0
- data/app/models/concerns/foreman_remote_execution/user_extensions.rb +9 -0
- data/app/models/input_template_renderer.rb +42 -0
- data/app/models/job_invocation.rb +21 -0
- data/app/models/job_invocation_composer.rb +210 -0
- data/app/models/job_template.rb +52 -0
- data/app/models/remote_execution_provider.rb +17 -0
- data/app/models/setting/remote_execution.rb +19 -0
- data/app/models/ssh_execution_provider.rb +2 -0
- data/app/models/targeting.rb +56 -0
- data/app/models/targeting_host.rb +9 -0
- data/app/models/template_input.rb +154 -0
- data/app/models/template_invocation.rb +13 -0
- data/app/models/template_invocation_input_value.rb +8 -0
- data/app/views/api/v2/job_templates/base.json.rabl +3 -0
- data/app/views/api/v2/job_templates/create.json.rabl +3 -0
- data/app/views/api/v2/job_templates/index.json.rabl +3 -0
- data/app/views/api/v2/job_templates/main.json.rabl +5 -0
- data/app/views/api/v2/job_templates/show.json.rabl +9 -0
- data/app/views/job_invocations/_form.html.erb +67 -0
- data/app/views/job_invocations/_tab_hosts.html.erb +33 -0
- data/app/views/job_invocations/_tab_overview.html.erb +41 -0
- data/app/views/job_invocations/index.html.erb +30 -0
- data/app/views/job_invocations/new.html.erb +8 -0
- data/app/views/job_invocations/refresh.js.erb +1 -0
- data/app/views/job_invocations/show.html.erb +21 -0
- data/app/views/job_templates/_custom_tab_headers.html.erb +2 -0
- data/app/views/job_templates/_custom_tabs.html.erb +28 -0
- data/app/views/job_templates/auto_complete_job_name.json.erb +3 -0
- data/app/views/job_templates/edit.html.erb +6 -0
- data/app/views/job_templates/index.html.erb +33 -0
- data/app/views/job_templates/new.html.erb +6 -0
- data/app/views/template_inputs/_form.html.erb +22 -0
- data/config/routes.rb +35 -0
- data/db/migrate/20150612121541_add_job_template_to_template.rb +6 -0
- data/db/migrate/20150616080015_create_template_input.rb +19 -0
- data/db/migrate/20150708133241_add_targeting.rb +25 -0
- data/db/migrate/20150708133242_add_invocation.rb +11 -0
- data/db/migrate/20150708133305_add_template_invocation.rb +22 -0
- data/db/migrate/20150812110800_add_resolved_at_to_targeting.rb +5 -0
- data/db/migrate/20150812145900_add_last_task_id_to_job_invocation.rb +6 -0
- data/db/seeds.d/60-ssh_proxy_feature.rb +2 -0
- data/lib/foreman_remote_execution/engine.rb +119 -0
- data/lib/foreman_remote_execution/version.rb +3 -0
- data/lib/foreman_remote_execution.rb +6 -0
- data/lib/tasks/foreman_remote_execution_tasks.rake +49 -0
- data/locale/Makefile +62 -0
- data/locale/en/foreman_remote_execution.po +19 -0
- data/locale/foreman_remote_execution.pot +19 -0
- data/locale/gemspec.rb +2 -0
- data/test/factories/foreman_remote_execution_factories.rb +48 -0
- data/test/functional/api/v2/job_templates_controller_test.rb +74 -0
- data/test/test_plugin_helper.rb +8 -0
- data/test/unit/actions/run_hosts_job_test.rb +40 -0
- data/test/unit/actions/run_proxy_command_test.rb +30 -0
- data/test/unit/input_template_renderer_test.rb +366 -0
- data/test/unit/job_invocation_composer_test.rb +415 -0
- data/test/unit/job_invocation_test.rb +31 -0
- data/test/unit/job_template_test.rb +5 -0
- data/test/unit/remote_execution_provider_test.rb +51 -0
- data/test/unit/targeting_test.rb +107 -0
- data/test/unit/template_input_test.rb +25 -0
- metadata +195 -0
|
@@ -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,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,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,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
|