foreman_remote_execution 1.8.2 → 1.8.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.babelrc +10 -3
- data/.eslintrc +1 -18
- data/app/controllers/api/v2/job_invocations_controller.rb +1 -0
- data/app/lib/actions/remote_execution/run_hosts_job.rb +1 -1
- data/app/models/job_invocation.rb +1 -1
- data/app/models/job_invocation_composer.rb +4 -3
- data/app/models/targeting.rb +7 -2
- data/app/views/api/v2/job_invocations/main.json.rabl +2 -1
- data/app/views/job_invocations/_card_target_hosts.html.erb +4 -0
- data/app/views/job_invocations/_form.html.erb +9 -1
- data/db/migrate/20180913101042_add_randomized_ordering_to_targeting.rb +5 -0
- data/db/seeds.d/100-assign_features_with_templates.rb +21 -0
- data/extra/cockpit/cockpit.conf.example +13 -0
- data/extra/cockpit/foreman-cockpit.service +9 -0
- data/lib/foreman_remote_execution/engine.rb +11 -0
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/package.json +6 -12
- data/test/factories/foreman_remote_execution_factories.rb +4 -0
- data/test/unit/job_invocation_test.rb +6 -1
- data/test/unit/targeting_test.rb +19 -0
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '088390c13c651a79849a384d7745fad23975bacbedf7913be50d6bed8d66db60'
|
4
|
+
data.tar.gz: 6138f9a4c9cdc374e21254c739ec3c45b5dd95ebd87bd04e40dce2b346874380
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c866a3ae708c68f9e2176817d74b27c185558cba82e5d3e0c9777fe6e32a538742fac1ff60c472ed8deda93890db3b81e7b0c38af3102dbe0b56590342559a0c
|
7
|
+
data.tar.gz: c4a1f91be44fb15b78ce1b4ff262b885bb3b325caa7a0ac6078203f3c3011e1ff93e97082869806e14a4428e95b0476cc451d267188bc41ab0b2c4e2eaba1bd3
|
data/.babelrc
CHANGED
@@ -3,7 +3,14 @@
|
|
3
3
|
"plugins": [
|
4
4
|
"transform-class-properties",
|
5
5
|
"transform-object-rest-spread",
|
6
|
-
"transform-object-assign"
|
7
|
-
|
8
|
-
|
6
|
+
"transform-object-assign"
|
7
|
+
],
|
8
|
+
"env": {
|
9
|
+
"test": {
|
10
|
+
"presets": ["@theforeman/vendor-dev/babel.preset.js"]
|
11
|
+
},
|
12
|
+
"storybook": {
|
13
|
+
"presets": ["@theforeman/vendor-dev/babel.preset.js"]
|
14
|
+
}
|
15
|
+
}
|
9
16
|
}
|
data/.eslintrc
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"root": true,
|
3
|
-
"extends": "airbnb-base",
|
3
|
+
"extends": ["airbnb-base", "./node_modules/@theforeman/vendor-dev/eslint.extends.js"],
|
4
4
|
"plugins": [
|
5
5
|
"react"
|
6
6
|
],
|
@@ -11,16 +11,6 @@
|
|
11
11
|
"jasmine": true,
|
12
12
|
"jest": true
|
13
13
|
},
|
14
|
-
"globals": {
|
15
|
-
"document": false,
|
16
|
-
"escape": false,
|
17
|
-
"navigator": false,
|
18
|
-
"unescape": false,
|
19
|
-
"window": false,
|
20
|
-
"$": true,
|
21
|
-
"_": true,
|
22
|
-
"__": true
|
23
|
-
},
|
24
14
|
"parser": "babel-eslint",
|
25
15
|
"rules": {
|
26
16
|
"react/jsx-uses-vars": "error",
|
@@ -35,13 +25,6 @@
|
|
35
25
|
"no-underscore-dangle": "off",
|
36
26
|
"no-use-before-define": "off",
|
37
27
|
"import/prefer-default-export": "off",
|
38
|
-
"import/no-extraneous-dependencies": [
|
39
|
-
"error",
|
40
|
-
{
|
41
|
-
// Allow importing devDependencies like @storybook
|
42
|
-
"devDependencies": true
|
43
|
-
}
|
44
|
-
],
|
45
28
|
// Import rules off for now due to HoundCI issue
|
46
29
|
"import/no-unresolved": "off",
|
47
30
|
"import/extensions": "off"
|
@@ -25,6 +25,7 @@ module Api
|
|
25
25
|
param :job_invocation, Hash, :required => true, :action_aware => true do
|
26
26
|
param :job_template_id, String, :required => false, :desc => N_('The job template to use, parameter is required unless feature was specified')
|
27
27
|
param :targeting_type, String, :required => true, :desc => N_('Invocation type, one of %s') % Targeting::TYPES
|
28
|
+
param :randomized_ordering, :bool, :desc => N_('Execute the jobs on hosts in randomized order')
|
28
29
|
param :inputs, Hash, :required => false, :desc => N_('Inputs to use')
|
29
30
|
param :ssh, Hash, :desc => N_('SSH provider specific options') do
|
30
31
|
param :effective_user, String,
|
@@ -23,7 +23,7 @@ class JobInvocation < ApplicationRecord
|
|
23
23
|
validates_associated :targeting, :all_template_invocations
|
24
24
|
|
25
25
|
scoped_search :on => :job_category, :complete_value => true
|
26
|
-
scoped_search :on => :description
|
26
|
+
scoped_search :on => :description, :complete_value => true
|
27
27
|
|
28
28
|
has_many :template_invocations_hosts, :through => :template_invocations, :source => :host
|
29
29
|
scoped_search :relation => :template_invocations_hosts, :on => :name, :rename => 'host', :complete_value => true
|
@@ -108,7 +108,7 @@ class JobInvocationComposer
|
|
108
108
|
|
109
109
|
def targeting_params
|
110
110
|
raise ::Foreman::Exception, _('Cannot specify both bookmark_id and search_query') if api_params[:bookmark_id] && api_params[:search_query]
|
111
|
-
api_params.slice(:targeting_type, :bookmark_id, :search_query).merge(:user_id => User.current.id)
|
111
|
+
api_params.slice(:targeting_type, :bookmark_id, :search_query, :randomized_ordering).merge(:user_id => User.current.id)
|
112
112
|
end
|
113
113
|
|
114
114
|
def triggering_params
|
@@ -205,7 +205,7 @@ class JobInvocationComposer
|
|
205
205
|
search_query = @host_ids.empty? ? 'name ^ ()' : Targeting.build_query_from_hosts(@host_ids)
|
206
206
|
base.merge(:search_query => search_query, :targeting_type => job_invocation.targeting.targeting_type)
|
207
207
|
else
|
208
|
-
base.merge job_invocation.targeting.attributes.slice('search_query', 'bookmark_id', 'targeting_type')
|
208
|
+
base.merge job_invocation.targeting.attributes.slice('search_query', 'bookmark_id', 'targeting_type', 'randomized_ordering')
|
209
209
|
end
|
210
210
|
end
|
211
211
|
|
@@ -512,7 +512,8 @@ class JobInvocationComposer
|
|
512
512
|
Targeting.new(
|
513
513
|
:bookmark_id => bookmark_id,
|
514
514
|
:targeting_type => params[:targeting][:targeting_type],
|
515
|
-
:search_query => query
|
515
|
+
:search_query => query,
|
516
|
+
:randomized_ordering => params[:targeting][:randomized_ordering]
|
516
517
|
) { |t| t.user_id = params[:targeting][:user_id] }
|
517
518
|
end
|
518
519
|
|
data/app/models/targeting.rb
CHANGED
@@ -5,11 +5,15 @@ class Targeting < ApplicationRecord
|
|
5
5
|
TYPES = { STATIC_TYPE => N_('Static Query'), DYNAMIC_TYPE => N_('Dynamic Query') }.freeze
|
6
6
|
RESOLVE_PERMISSION = :view_hosts
|
7
7
|
|
8
|
+
ORDERED = 'ordered_execution'.freeze
|
9
|
+
RANDOMIZED = 'randomized_execution'.freeze
|
10
|
+
ORDERINGS = { ORDERED => N_('Alphabetical'), RANDOMIZED => N_('Randomized') }.freeze
|
11
|
+
|
8
12
|
belongs_to :user
|
9
13
|
belongs_to :bookmark
|
10
14
|
|
11
15
|
has_many :targeting_hosts, :dependent => :destroy
|
12
|
-
has_many :hosts, :through => :targeting_hosts
|
16
|
+
has_many :hosts, -> { order TargetingHost.table_name + '.id' }, :through => :targeting_hosts
|
13
17
|
has_one :job_invocation, :dependent => :delete
|
14
18
|
has_many :template_invocations, :through => :job_invocation
|
15
19
|
|
@@ -40,7 +44,8 @@ class Targeting < ApplicationRecord
|
|
40
44
|
self.validate!
|
41
45
|
# avoid validation of hosts objects - they will be loaded for no reason.
|
42
46
|
# pluck(:id) returns duplicate results for HostCollections
|
43
|
-
host_ids = User.as(user.login) { Host.authorized(RESOLVE_PERMISSION, Host).search_for(search_query).pluck(:id).uniq }
|
47
|
+
host_ids = User.as(user.login) { Host.authorized(RESOLVE_PERMISSION, Host).search_for(search_query).order(:name, :id).pluck(:id).uniq }
|
48
|
+
host_ids.shuffle!(random: Random.new) if randomized_ordering
|
44
49
|
# this can be optimized even more, by introducing bulk insert
|
45
50
|
self.targeting_hosts.build(host_ids.map { |id| { :host_id => id } })
|
46
51
|
self.save(:validate => false)
|
@@ -16,7 +16,8 @@ node do |invocation|
|
|
16
16
|
end
|
17
17
|
|
18
18
|
child :targeting do
|
19
|
-
attributes :bookmark_id, :search_query, :targeting_type, :user_id, :status, :status_label
|
19
|
+
attributes :bookmark_id, :search_query, :targeting_type, :user_id, :status, :status_label,
|
20
|
+
:randomized_ordering
|
20
21
|
|
21
22
|
child :hosts do
|
22
23
|
extends 'api/v2/hosts/base'
|
@@ -16,6 +16,10 @@
|
|
16
16
|
<strong><%= _(Targeting::TYPES[job_invocation.targeting.targeting_type]).downcase %></strong>
|
17
17
|
<pre><%= job_invocation.targeting.search_query %></pre>
|
18
18
|
</p>
|
19
|
+
<p>
|
20
|
+
<% key = job_invocation.targeting.randomized_ordering ? Targeting::RANDOMIZED : Targeting::ORDERED %>
|
21
|
+
<%= _('Execution order') %>: <strong><%= Targeting::ORDERINGS[key].downcase %></strong>
|
22
|
+
</p>
|
19
23
|
</div>
|
20
24
|
<div class='card-pf-footer'>
|
21
25
|
<p>
|
@@ -104,13 +104,21 @@
|
|
104
104
|
</div>
|
105
105
|
|
106
106
|
<div class="form-group advanced hidden">
|
107
|
+
<%= add_label({ :label => _('Execution ordering'), :label_help => _("Execution ordering determines whether the jobs should be executed on hosts in alphabetical order or in randomized order.<br><ul><li><b>Ordered</b> - executes the jobs on hosts in alphabetical order</li><li><b>Randomized</b> - randomizes the order in which jobs are executed on hosts</li></ul>") }, f, :randomized_ordering) %>
|
108
|
+
|
109
|
+
<div class="col-md-4">
|
110
|
+
<%= radio_button_f targeting_fields, :randomized_ordering, :value => false, :text => _(Targeting::ORDERINGS[Targeting::ORDERED]), :checked => !@composer.targeting.randomized_ordering %>
|
111
|
+
<%= radio_button_f targeting_fields, :randomized_ordering, :value => true, :text => _(Targeting::ORDERINGS[Targeting::RANDOMIZED]) %>
|
112
|
+
</div>
|
113
|
+
</div>
|
114
|
+
|
115
|
+
<div class="form-group">
|
107
116
|
<%= add_label({ :label => _('Type of query'), :label_help => _("Type has impact on when is the query evaluated to hosts.<br><ul><li><b>Static</b> - evaluates just after you submit this form</li><li><b>Dynamic</b> - evaluates just before the execution is started, so if it's planed in future, targeted hosts set may change before it</li></ul>") }, f, :targetting_type) %>
|
108
117
|
|
109
118
|
<div class="col-md-4">
|
110
119
|
<%= radio_button_f targeting_fields, :targeting_type, :value => Targeting::STATIC_TYPE, :text => _(Targeting::TYPES[Targeting::STATIC_TYPE]) %>
|
111
120
|
<%= radio_button_f targeting_fields, :targeting_type, :value => Targeting::DYNAMIC_TYPE, :text => _(Targeting::TYPES[Targeting::DYNAMIC_TYPE]) %>
|
112
121
|
</div>
|
113
|
-
|
114
122
|
</div>
|
115
123
|
<% end %>
|
116
124
|
|
@@ -0,0 +1,21 @@
|
|
1
|
+
User.as_anonymous_admin do
|
2
|
+
RemoteExecutionFeature.without_auditing do
|
3
|
+
if Rails.env.test? || Foreman.in_rake?
|
4
|
+
# If this file tries to import a template with a REX feature in a SeedsTest,
|
5
|
+
# it will fail - the REX feature isn't registered on SeedsTest because
|
6
|
+
# DatabaseCleaner truncates the db before every test.
|
7
|
+
# During db:seed, we also want to know the feature is registered before
|
8
|
+
# seeding the template
|
9
|
+
# kudos to dLobatog
|
10
|
+
ForemanRemoteExecution.register_rex_feature
|
11
|
+
end
|
12
|
+
JobTemplate.without_auditing do
|
13
|
+
module_template = JobTemplate.find_by(name: 'Puppet Run Once - SSH Default')
|
14
|
+
if module_template && !Rails.env.test? && Setting[:remote_execution_sync_templates]
|
15
|
+
module_template.sync_feature('puppet_run_host')
|
16
|
+
module_template.organizations << Organization.unscoped.all if module_template.organizations.empty?
|
17
|
+
module_template.locations << Location.unscoped.all if module_template.locations.empty?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
[Service]
|
2
|
+
Environment=XDG_CONFIG_DIRS=/etc/foreman/
|
3
|
+
Environment=FOREMAN_COCKPIT_SETTINGS=/etc/foreman/cockpit/foreman-cockpit-session.yml
|
4
|
+
ExecStart=/usr/libexec/cockpit-ws --no-tls --address 127.0.0.1 --port 9999
|
5
|
+
User=foreman
|
6
|
+
Group=foreman
|
7
|
+
|
8
|
+
[Install]
|
9
|
+
WantedBy=multi-user.target
|
@@ -180,6 +180,8 @@ module ForemanRemoteExecution
|
|
180
180
|
ForemanTasks::Task.send(:include, ForemanRemoteExecution::ForemanTasksTaskExtensions)
|
181
181
|
ForemanTasks::Cleaner.send(:include, ForemanRemoteExecution::ForemanTasksCleanerExtensions)
|
182
182
|
RemoteExecutionProvider.register(:SSH, SSHExecutionProvider)
|
183
|
+
|
184
|
+
ForemanRemoteExecution.register_rex_feature
|
183
185
|
end
|
184
186
|
|
185
187
|
initializer 'foreman_remote_execution.register_gettext', after: :load_config_initializers do |_app|
|
@@ -188,4 +190,13 @@ module ForemanRemoteExecution
|
|
188
190
|
Foreman::Gettext::Support.add_text_domain locale_domain, locale_dir
|
189
191
|
end
|
190
192
|
end
|
193
|
+
|
194
|
+
def self.register_rex_feature
|
195
|
+
RemoteExecutionFeature.register(
|
196
|
+
:puppet_run_host,
|
197
|
+
N_('Run Puppet Once'),
|
198
|
+
:description => N_('Perform a single Puppet run'),
|
199
|
+
:host_action_button => true
|
200
|
+
)
|
201
|
+
end
|
191
202
|
end
|
data/package.json
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
"version": "1.0.0",
|
4
4
|
"license": "GPL-3.0",
|
5
5
|
"scripts": {
|
6
|
-
"lint": "./node_modules/.bin/eslint -c .eslintrc webpack/ script/
|
6
|
+
"lint": "./node_modules/.bin/eslint -c .eslintrc webpack/ script/",
|
7
7
|
"test": "node node_modules/.bin/jest webpack",
|
8
8
|
"test:watch": "node node_modules/.bin/jest webpack --watchAll",
|
9
9
|
"test:current": "node node_modules/.bin/jest webpack --watch"
|
@@ -31,13 +31,14 @@
|
|
31
31
|
"url": "http://projects.theforeman.org/projects/foreman_remote_execution/issues"
|
32
32
|
},
|
33
33
|
"devDependencies": {
|
34
|
+
"@theforeman/vendor-dev": "^0.1.1",
|
34
35
|
"babel-eslint": "^8.2.1",
|
35
|
-
"babel-preset-env": "^1.6.0",
|
36
|
-
"babel-preset-react": "^6.24.1",
|
37
36
|
"babel-plugin-lodash": "^3.3.2",
|
38
|
-
"babel-plugin-transform-object-assign": "^6.22.0",
|
39
37
|
"babel-plugin-transform-class-properties": "^6.24.1",
|
38
|
+
"babel-plugin-transform-object-assign": "^6.22.0",
|
40
39
|
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
40
|
+
"babel-preset-env": "^1.6.0",
|
41
|
+
"babel-preset-react": "^6.24.1",
|
41
42
|
"enzyme": "^3.2.0",
|
42
43
|
"enzyme-adapter-react-16": "^1.1.0",
|
43
44
|
"enzyme-to-json": "^3.1.2",
|
@@ -50,13 +51,6 @@
|
|
50
51
|
"jest": "^21.2.1"
|
51
52
|
},
|
52
53
|
"dependencies": {
|
53
|
-
"
|
54
|
-
"prop-types": "^15.6.0",
|
55
|
-
"react": "^16.2.0",
|
56
|
-
"react-dom": "^16.2.0",
|
57
|
-
"react-redux": "^5.0.6",
|
58
|
-
"redux": "^3.7.2",
|
59
|
-
"seamless-immutable": "^7.1.3",
|
60
|
-
"urijs": "^1.19.0"
|
54
|
+
"@theforeman/vendor": "^0.1.1"
|
61
55
|
}
|
62
56
|
}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'test_plugin_helper'
|
2
2
|
|
3
3
|
class JobInvocationTest < ActiveSupport::TestCase
|
4
|
-
let(:job_invocation) { FactoryBot.build(:job_invocation) }
|
4
|
+
let(:job_invocation) { FactoryBot.build(:job_invocation, :description => 'A text with "quotes"') }
|
5
5
|
let(:template) { FactoryBot.create(:job_template, :with_input) }
|
6
6
|
|
7
7
|
context 'search for job invocations' do
|
@@ -13,6 +13,11 @@ class JobInvocationTest < ActiveSupport::TestCase
|
|
13
13
|
found_jobs = JobInvocation.search_for(%{job_category = "#{job_invocation.job_category}"}).paginate(:page => 1).with_task.order('job_invocations.id DESC')
|
14
14
|
found_jobs.must_equal [job_invocation]
|
15
15
|
end
|
16
|
+
|
17
|
+
it 'is able to auto complete description' do
|
18
|
+
expected = 'description = "A text with \"quotes\""'
|
19
|
+
JobInvocation.complete_for('description = ').must_equal [expected]
|
20
|
+
end
|
16
21
|
end
|
17
22
|
|
18
23
|
context 'able to be created' do
|
data/test/unit/targeting_test.rb
CHANGED
@@ -117,4 +117,23 @@ class TargetingTest < ActiveSupport::TestCase
|
|
117
117
|
end
|
118
118
|
end
|
119
119
|
end
|
120
|
+
|
121
|
+
context 'randomized ordering' do
|
122
|
+
let(:targeting) { FactoryBot.build(:targeting, :with_randomized_ordering) }
|
123
|
+
let(:hosts) { (0..4).map { FactoryBot.create(:host) } }
|
124
|
+
|
125
|
+
it 'loads the hosts in random order' do
|
126
|
+
rng = Random.new(4) # Chosen by a fair dice roll
|
127
|
+
Random.stubs(:new).returns(rng)
|
128
|
+
hosts
|
129
|
+
targeting.search_query = 'name ~ host*'
|
130
|
+
targeting.user = users(:admin)
|
131
|
+
targeting.resolve_hosts!
|
132
|
+
randomized_host_ids = targeting.hosts.map(&:id)
|
133
|
+
host_ids = hosts.map(&:id)
|
134
|
+
|
135
|
+
assert_not_equal host_ids, randomized_host_ids
|
136
|
+
assert_equal host_ids, randomized_host_ids.sort
|
137
|
+
end
|
138
|
+
end
|
120
139
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman_remote_execution
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.8.
|
4
|
+
version: 1.8.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Foreman Remote Execution team
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-08-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: deface
|
@@ -325,12 +325,16 @@ files:
|
|
325
325
|
- db/migrate/20180202123215_add_feature_id_to_job_invocation.rb
|
326
326
|
- db/migrate/20180226095631_change_task_id_to_uuid.rb
|
327
327
|
- db/migrate/20180411160809_add_sudo_password_to_job_invocation.rb
|
328
|
+
- db/migrate/20180913101042_add_randomized_ordering_to_targeting.rb
|
328
329
|
- db/migrate/20190111153330_remove_remote_execution_without_proxy_setting.rb
|
330
|
+
- db/seeds.d/100-assign_features_with_templates.rb
|
329
331
|
- db/seeds.d/50-notification_blueprints.rb
|
330
332
|
- db/seeds.d/60-ssh_proxy_feature.rb
|
331
333
|
- db/seeds.d/70-job_templates.rb
|
332
334
|
- db/seeds.d/90-bookmarks.rb
|
335
|
+
- extra/cockpit/cockpit.conf.example
|
333
336
|
- extra/cockpit/foreman-cockpit-session
|
337
|
+
- extra/cockpit/foreman-cockpit.service
|
334
338
|
- extra/cockpit/settings.yml.example
|
335
339
|
- foreman_remote_execution.gemspec
|
336
340
|
- lib/foreman_remote_execution.rb
|
@@ -424,7 +428,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
424
428
|
version: '0'
|
425
429
|
requirements: []
|
426
430
|
rubyforge_project:
|
427
|
-
rubygems_version: 2.7.
|
431
|
+
rubygems_version: 2.7.6
|
428
432
|
signing_key:
|
429
433
|
specification_version: 4
|
430
434
|
summary: A plugin bringing remote execution to the Foreman, completing the config
|