foreman_ansible 2.1.0 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/app/controllers/api/v2/ansible_roles_controller.rb +10 -5
- data/app/controllers/foreman_ansible/api/v2/hostgroups_controller_extensions.rb +1 -1
- data/app/controllers/foreman_ansible/api/v2/hosts_controller_extensions.rb +1 -1
- data/app/models/ansible_role.rb +9 -0
- data/app/models/concerns/foreman_ansible/host_managed_extensions.rb +1 -0
- data/app/models/concerns/foreman_ansible/hostgroup_extensions.rb +1 -0
- data/app/models/host_ansible_role.rb +0 -1
- data/app/models/setting/ansible.rb +15 -0
- data/app/services/foreman_ansible/insights_notification_builder.rb +64 -0
- data/app/services/foreman_ansible/insights_plan_runner.rb +17 -2
- data/app/services/foreman_ansible/inventory_creator.rb +10 -0
- data/app/services/foreman_ansible/renderer_methods.rb +10 -3
- data/app/services/foreman_ansible/ui_roles_importer.rb +0 -2
- data/app/views/api/v2/ansible_roles/show.json.rabl +1 -1
- data/app/views/foreman_ansible/job_templates/package_action_-_ansible_default.erb +4 -0
- data/app/views/foreman_ansible/job_templates/power_action_-_ansible_default.erb +2 -2
- data/app/views/foreman_ansible/job_templates/puppet_run_once_-_ansible_default.erb +1 -1
- data/app/views/foreman_ansible/job_templates/run_command_-_ansible_default.erb +2 -1
- data/app/views/foreman_ansible/job_templates/service_action_-_ansible_default.erb +1 -1
- data/db/migrate/20180410125416_rename_ansible_job_categories.rb +30 -0
- data/db/seeds.d/75_job_templates.rb +18 -16
- data/db/seeds.d/90_notification_blueprints.rb +17 -0
- data/lib/foreman_ansible/engine.rb +1 -2
- data/lib/foreman_ansible/register.rb +1 -0
- data/lib/foreman_ansible/remote_execution.rb +4 -1
- data/lib/foreman_ansible/version.rb +1 -1
- data/locale/en/foreman_ansible.po +130 -7
- data/locale/foreman_ansible.pot +224 -34
- data/test/fixtures/insights_playbook.yaml +79 -0
- data/test/unit/services/insights_plan_runner_test.rb +36 -0
- data/test/unit/services/inventory_creator_test.rb +18 -0
- metadata +32 -47
- data/webpack/components/ReportJsonViewer.js +0 -17
- data/webpack/index.js +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7fa908b35593b08f7be264f7a9d23ef2357858ab24faea00711b06084a4f9c4a
|
4
|
+
data.tar.gz: 7d1ae5b38052fd0b620170316ccf295181ff02c7d92712db15a95e2c3ab56bb5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0c0fc2260f5762f172b3445567069b48bc7a021129d9aa8ae94ff4eef7d28cadd7f93fa8302e6143561545fc2b327a51e5f1400942ad853bed4262c431f5d751
|
7
|
+
data.tar.gz: d01daf2b7f4559c9e4ec4e819f6d4d99ce4570f59d3e4b99a2e074b3c16046817634846f534a08c46ec0c4aadbd92489897fd02a8c5f6b37e428605fa10daad3
|
@@ -4,33 +4,38 @@ module Api
|
|
4
4
|
class AnsibleRolesController < ::Api::V2::BaseController
|
5
5
|
include ::Api::Version2
|
6
6
|
|
7
|
+
resource_description do
|
8
|
+
api_version 'v2'
|
9
|
+
api_base_url '/ansible/api'
|
10
|
+
end
|
11
|
+
|
7
12
|
before_action :find_resource, :only => [:show, :destroy]
|
8
13
|
before_action :find_proxy, :only => [:import, :obsolete]
|
9
14
|
before_action :create_importer, :only => [:import, :obsolete]
|
10
15
|
|
11
|
-
api :GET, '/
|
16
|
+
api :GET, '/ansible_roles/:id', N_('Show role')
|
12
17
|
param :id, :identifier, :required => true
|
13
18
|
def show; end
|
14
19
|
|
15
|
-
api :GET, '/
|
20
|
+
api :GET, '/ansible_roles', N_('List Ansible roles')
|
16
21
|
param_group :search_and_pagination, ::Api::V2::BaseController
|
17
22
|
def index
|
18
23
|
@ansible_roles = resource_scope_for_index
|
19
24
|
end
|
20
25
|
|
21
|
-
api :DELETE, '/
|
26
|
+
api :DELETE, '/ansible_roles/:id', N_('Deletes Ansible role')
|
22
27
|
param :id, :identifier, :required => true
|
23
28
|
def destroy
|
24
29
|
process_response @ansible_role.destroy
|
25
30
|
end
|
26
31
|
|
27
|
-
api :POST, '/
|
32
|
+
api :POST, '/ansible_roles/import', N_('Import Ansible roles')
|
28
33
|
param :proxy, Hash, N_('Smart Proxy to import from')
|
29
34
|
def import
|
30
35
|
@imported = @importer.import!
|
31
36
|
end
|
32
37
|
|
33
|
-
api :POST, '/
|
38
|
+
api :POST, '/ansible_roles/obsolete', N_('Obsolete Ansible roles')
|
34
39
|
param :proxy, Hash, N_('Smart Proxy to import from')
|
35
40
|
def obsolete
|
36
41
|
@obsoleted = @importer.obsolete!
|
@@ -27,7 +27,7 @@ module ForemanAnsible
|
|
27
27
|
def multiple_play_roles
|
28
28
|
find_multiple
|
29
29
|
composer = job_composer(:ansible_run_host,
|
30
|
-
@hostgroups.map(&:
|
30
|
+
@hostgroups.map(&:host_ids).flatten.uniq)
|
31
31
|
process_response composer.trigger!, composer.job_invocation
|
32
32
|
end
|
33
33
|
end
|
@@ -21,7 +21,7 @@ module ForemanAnsible
|
|
21
21
|
param :id, Array, :required => true
|
22
22
|
|
23
23
|
def multiple_play_roles
|
24
|
-
composer = job_composer(:ansible_run_host, @host)
|
24
|
+
composer = job_composer(:ansible_run_host, @host.pluck(:id))
|
25
25
|
process_response composer.trigger!, composer.job_invocation
|
26
26
|
end
|
27
27
|
end
|
data/app/models/ansible_role.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# Simple model to store basic info about the Ansible role
|
2
2
|
class AnsibleRole < ApplicationRecord
|
3
|
+
audited
|
3
4
|
include Authorizable
|
4
5
|
|
5
6
|
self.include_root_in_json = false
|
@@ -12,6 +13,14 @@ class AnsibleRole < ApplicationRecord
|
|
12
13
|
|
13
14
|
scoped_search :on => :name, :complete_value => true
|
14
15
|
scoped_search :on => :updated_at
|
16
|
+
scoped_search :relation => :hosts,
|
17
|
+
:on => :id, :rename => :host_id, :only_explicit => true
|
18
|
+
scoped_search :relation => :hosts,
|
19
|
+
:on => :name, :rename => :host, :only_explicit => true
|
20
|
+
scoped_search :relation => :hostgroups,
|
21
|
+
:on => :id, :rename => :hostgroup_id, :only_explicit => true
|
22
|
+
scoped_search :relation => :hostgroups,
|
23
|
+
:on => :name, :rename => :hostgroup, :only_explicit => true
|
15
24
|
|
16
25
|
# Methods to be allowed in any template with safemode enabled
|
17
26
|
class Jail < Safemode::Jail
|
@@ -8,6 +8,7 @@ module ForemanAnsible
|
|
8
8
|
has_many :ansible_roles, :through => :hostgroup_ansible_roles,
|
9
9
|
:dependent => :destroy
|
10
10
|
include ForemanAnsible::HasManyAnsibleRoles
|
11
|
+
audit_associations :ansible_roles
|
11
12
|
|
12
13
|
def inherited_ansible_roles
|
13
14
|
ancestors.reduce([]) do |roles, hostgroup|
|
@@ -73,6 +73,21 @@ class Setting
|
|
73
73
|
'foreman_params["host_parameter"] in the playbooks.'),
|
74
74
|
true,
|
75
75
|
N_('Top level Ansible variables')
|
76
|
+
),
|
77
|
+
set(
|
78
|
+
'ansible_interval',
|
79
|
+
N_('Timeout (in minutes) when hosts should have reported.'),
|
80
|
+
'30',
|
81
|
+
N_('Ansible report timeout')
|
82
|
+
),
|
83
|
+
set(
|
84
|
+
'ansible_out_of_sync_disabled',
|
85
|
+
format(N_('Disable host configuration status turning to out of'\
|
86
|
+
' sync for %{cfgmgmt} after report does not arrive within'\
|
87
|
+
' configured interval'), :cfgmgmt => 'Ansible'),
|
88
|
+
false,
|
89
|
+
format(N_('%{cfgmgmt} out of sync disabled'),
|
90
|
+
:cfgmgmt => 'Ansible')
|
76
91
|
)
|
77
92
|
].compact.each do |s|
|
78
93
|
create(s.update(:category => 'Setting::Ansible'))
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module ForemanAnsible
|
2
|
+
# A class that builds custom notificaton for REX job if it's insights
|
3
|
+
# remediation feature
|
4
|
+
# rubocop:disable LineLength
|
5
|
+
class InsightsNotificationBuilder < ::UINotifications::RemoteExecutionJobs::BaseJobFinish
|
6
|
+
# rubocop:enable LineLength
|
7
|
+
def deliver!
|
8
|
+
::Notification.create!(
|
9
|
+
:audience => Notification::AUDIENCE_USER,
|
10
|
+
:notification_blueprint => blueprint,
|
11
|
+
:initiator => initiator,
|
12
|
+
:message => message,
|
13
|
+
:subject => subject,
|
14
|
+
:actions => {
|
15
|
+
:links => links
|
16
|
+
}
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def blueprint
|
21
|
+
name = 'insights_remediation_successful'
|
22
|
+
@blueprint ||= NotificationBlueprint.unscoped.find_by(:name => name)
|
23
|
+
end
|
24
|
+
|
25
|
+
def hosts_count
|
26
|
+
@hosts_count ||= subject.template_invocations_hosts.size
|
27
|
+
end
|
28
|
+
|
29
|
+
def message
|
30
|
+
UINotifications::StringParser.new(blueprint.message,
|
31
|
+
:hosts_count => hosts_count)
|
32
|
+
end
|
33
|
+
|
34
|
+
def links
|
35
|
+
job_links + insights_links
|
36
|
+
end
|
37
|
+
|
38
|
+
def insights_links
|
39
|
+
pattern_template = subject.pattern_template_invocations.first
|
40
|
+
plan_id = pattern_template.input_values.
|
41
|
+
joins(:template_input).
|
42
|
+
where('template_inputs.name' => 'plan_id').
|
43
|
+
first.try(:value)
|
44
|
+
return [] if plan_id.nil?
|
45
|
+
|
46
|
+
[
|
47
|
+
{
|
48
|
+
:href => "/redhat_access/insights/planner/#{plan_id}",
|
49
|
+
:title => _('Remediation Plan')
|
50
|
+
}
|
51
|
+
]
|
52
|
+
end
|
53
|
+
|
54
|
+
def job_links
|
55
|
+
UINotifications::URLResolver.new(
|
56
|
+
subject,
|
57
|
+
:links => [{
|
58
|
+
:path_method => :job_invocation_path,
|
59
|
+
:title => _('Job Details')
|
60
|
+
}]
|
61
|
+
).actions[:links]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -32,8 +32,23 @@ if defined?(RedhatAccess)
|
|
32
32
|
"v3/maintenance/#{@plan_id}/playbook",
|
33
33
|
get_ssl_options_for_org(@organization, nil)
|
34
34
|
)
|
35
|
-
|
36
|
-
YAML.safe_load(
|
35
|
+
@raw_playbook = resource.get.body
|
36
|
+
YAML.safe_load(@raw_playbook)
|
37
|
+
end
|
38
|
+
|
39
|
+
# To parse the disclaimer we iterate over the first lines of the
|
40
|
+
# playbook (all comments) until we get to a line that looks like
|
41
|
+
# "Generated by Red Hat Insights on..."
|
42
|
+
def parse_disclaimer(playbook = @raw_playbook)
|
43
|
+
return '' if playbook.blank?
|
44
|
+
disclaimer = []
|
45
|
+
playbook.split("\n").each do |line|
|
46
|
+
next if line == '---'
|
47
|
+
break unless line[0] == '#'
|
48
|
+
disclaimer << line
|
49
|
+
break if /Generated by Red Hat Insights on/ =~ line
|
50
|
+
end
|
51
|
+
disclaimer.join("\n")
|
37
52
|
end
|
38
53
|
|
39
54
|
# This method creates a hash like this:
|
@@ -94,6 +94,7 @@ module ForemanAnsible
|
|
94
94
|
'ansible_become' => @template_invocation.effective_user,
|
95
95
|
'ansible_user' => host_setting(host, 'remote_execution_ssh_user'),
|
96
96
|
'ansible_ssh_pass' => rex_ssh_password(host),
|
97
|
+
'ansible_ssh_private_key_file' => ansible_or_rex_ssh_private_key(host),
|
97
98
|
'ansible_port' => host_setting(host, 'remote_execution_ssh_port')
|
98
99
|
}
|
99
100
|
# Backward compatibility for Ansible 1.x
|
@@ -115,6 +116,15 @@ module ForemanAnsible
|
|
115
116
|
host_setting(host, 'remote_execution_ssh_password')
|
116
117
|
end
|
117
118
|
|
119
|
+
def ansible_or_rex_ssh_private_key(host)
|
120
|
+
ansible_private_file = host_setting(host, 'ansible_ssh_private_key_file')
|
121
|
+
if !ansible_private_file.empty?
|
122
|
+
ansible_private_file
|
123
|
+
else
|
124
|
+
ForemanRemoteExecutionCore.settings[:ssh_identity_key_file]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
118
128
|
private
|
119
129
|
|
120
130
|
def render_rabl(host, template)
|
@@ -9,13 +9,20 @@ module ForemanAnsible
|
|
9
9
|
plan_id
|
10
10
|
)
|
11
11
|
rules = insights_plan.playbook
|
12
|
+
disclaimer = insights_plan.parse_disclaimer
|
12
13
|
hostname_rules_relation = insights_plan.hostname_rules(rules)
|
13
14
|
global_rules = insights_plan.rules_to_hash(rules)
|
14
|
-
host_playbooks = hostname_rules_relation
|
15
|
-
|
15
|
+
host_playbooks = individual_host_playbooks(hostname_rules_relation,
|
16
|
+
global_rules)
|
17
|
+
"#{disclaimer}\n#{host_playbooks.to_yaml}"
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def individual_host_playbooks(hostname_rules_relation, global_rules)
|
23
|
+
hostname_rules_relation[@host.name].reduce([]) do |acc, cur|
|
16
24
|
acc << global_rules[cur]
|
17
25
|
end
|
18
|
-
host_playbooks.to_yaml
|
19
26
|
end
|
20
27
|
end
|
21
28
|
end
|
@@ -11,7 +11,6 @@ module ForemanAnsible
|
|
11
11
|
delete_old_roles changes['obsolete'] if changes['obsolete']
|
12
12
|
end
|
13
13
|
|
14
|
-
# rubocop:disable Performance/HashEachMethods
|
15
14
|
def create_new_roles(changes)
|
16
15
|
changes.values.each do |new_role|
|
17
16
|
::AnsibleRole.create(JSON.parse(new_role))
|
@@ -23,6 +22,5 @@ module ForemanAnsible
|
|
23
22
|
::AnsibleRole.find(JSON.parse(old_role)['id']).destroy
|
24
23
|
end
|
25
24
|
end
|
26
|
-
# rubocop:enable Performance/HashEachMethods
|
27
25
|
end
|
28
26
|
end
|
@@ -35,11 +35,15 @@ model: JobTemplate
|
|
35
35
|
# For Windows targets use the win_package module instead.
|
36
36
|
---
|
37
37
|
- hosts: all
|
38
|
+
<%- if input('pre_script').present? -%>
|
38
39
|
pre_tasks:
|
39
40
|
- shell: "<%= input('pre_script') %>"
|
41
|
+
<%- end -%>
|
40
42
|
tasks:
|
41
43
|
- package:
|
42
44
|
name: <%= input('name') %>
|
43
45
|
state: <%= input('state') %>
|
46
|
+
<%- if input('post_script').present? -%>
|
44
47
|
post_tasks:
|
45
48
|
- shell: "<%= input('post_script') %>"
|
49
|
+
<%- end -%>
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<%#
|
2
2
|
name: Power Action - Ansible Default
|
3
|
-
job_category: Power
|
3
|
+
job_category: Ansible Power
|
4
4
|
description_format: "%{action} host"
|
5
5
|
snippet: false
|
6
6
|
template_inputs:
|
@@ -22,7 +22,7 @@ model: JobTemplate
|
|
22
22
|
echo <%= input('action') %> host && sleep 3
|
23
23
|
<%= case input('action')
|
24
24
|
when 'restart'
|
25
|
-
'
|
25
|
+
'shutdown -r +1'
|
26
26
|
else
|
27
27
|
'shutdown -h now'
|
28
28
|
end %>
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class RenameAnsibleJobCategories < ActiveRecord::Migration[5.1]
|
2
|
+
def up
|
3
|
+
unless User.unscoped.find_by_login(User::ANONYMOUS_ADMIN)
|
4
|
+
puts "No ANONYMOUS_ADMIN found. Skipping renaming Ansible jobs"
|
5
|
+
return
|
6
|
+
end
|
7
|
+
User.as_anonymous_admin do
|
8
|
+
updated_templates = ['Power Action - Ansible Default',
|
9
|
+
'Puppet Run Once - Ansible Default']
|
10
|
+
JobTemplate.without_auditing do
|
11
|
+
job_templates = JobTemplate.where(
|
12
|
+
:name => updated_templates
|
13
|
+
).all
|
14
|
+
job_templates.each do |job_template|
|
15
|
+
next if job_template.job_category =~ /^Ansible/
|
16
|
+
job_template.job_category = "Ansible #{job_template.job_category}"
|
17
|
+
job_template.save_without_auditing
|
18
|
+
end
|
19
|
+
|
20
|
+
service_template = JobTemplate.where(
|
21
|
+
:name => 'Service Action - Ansible Default'
|
22
|
+
).first
|
23
|
+
if service_template.present?
|
24
|
+
service_template.job_category = 'Ansible Services'
|
25
|
+
service_template.save_without_auditing
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -1,20 +1,22 @@
|
|
1
1
|
User.as_anonymous_admin do
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
2
|
+
RemoteExecutionFeature.without_auditing do
|
3
|
+
if Rails.env.test? || File.basename($PROGRAM_NAME) == '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
|
+
ForemanAnsible::Engine.register_rex_feature
|
10
|
+
end
|
11
|
+
JobTemplate.without_auditing do
|
12
|
+
Dir[File.join("#{ForemanAnsible::Engine.root}/app/views/foreman_ansible/"\
|
13
|
+
'job_templates/**/*.erb')].each do |template|
|
14
|
+
sync = !Rails.env.test? && Setting[:remote_execution_sync_templates]
|
15
|
+
JobTemplate.import_raw!(File.read(template),
|
16
|
+
:default => true,
|
17
|
+
:locked => true,
|
18
|
+
:update => sync)
|
19
|
+
end
|
18
20
|
end
|
19
21
|
end
|
20
22
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
blueprints = [
|
2
|
+
{
|
3
|
+
:group => N_('Jobs'),
|
4
|
+
:name => 'insights_remediation_successful',
|
5
|
+
:message => N_('Insights remediation on %{hosts_count}' \
|
6
|
+
' host(s) has finished successfully'),
|
7
|
+
:level => 'success',
|
8
|
+
:actions => {
|
9
|
+
:links => [
|
10
|
+
:path_method => :job_invocation_path,
|
11
|
+
:title => N_('Job Details')
|
12
|
+
]
|
13
|
+
}
|
14
|
+
}
|
15
|
+
]
|
16
|
+
|
17
|
+
blueprints.each { |blueprint| UINotifications::Seed.new(blueprint).configure }
|