foreman_maintain 1.9.2 → 1.10.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 353f969fb5c1563fa146f6f5cca6a9a5c0a42e22564aa6e26d0d5d2b58d1b943
4
- data.tar.gz: b9f71f13a94ffef3cf8b338faf4d43f5aa053c58f84327713581c2f8b8bc6fa8
3
+ metadata.gz: f0e6da9a83b7060020dd78da8a1fd8887e4b76708471b3ee80802335e5252224
4
+ data.tar.gz: 4fdb27b3583aa2665bd76e725d748408c19e956c9959ed2513d17eb234ae4e7b
5
5
  SHA512:
6
- metadata.gz: 81316471839fc3ac86ecc96edccecdf4b6475bdd908ab79c1d55eba6d07a8bad0e75aaa83a440449fc8bf357e1c93df87e0d5f45f4211ba602172be9fde31bd4
7
- data.tar.gz: ae7ae727511fe75f9c517de28a93c66231c5e6e2a362541f769a5d99c364f4b49f9bf80b178b1def23fd6f4d83596fdc0d83e06e89540a17d0751bc838347797
6
+ metadata.gz: 8cd917d7a19d07c9674b8c7a31ffd45378d936b27fa8442ad18ebf004d1b9084fba86b9e67747c66f4dc91a23b0426c7e1fb9442f501678ece2b950eb45efe7d
7
+ data.tar.gz: 965c7051bc9623d274d7c38b0a13fb4919f9e84d5651e22cb8b7faf827e9fc0b1f883c9154fae72f56b33c0fa5fe4cae6fd5b4432ebc44d0cc943c8b44982926
@@ -12,7 +12,7 @@ class Checks::CheckIpv6Disable < ForemanMaintain::Check
12
12
 
13
13
  def error_message
14
14
  base = "\nThe kernel contains ipv6.disable=1 which is known to break installation and upgrade"\
15
- ", remove and reboot before continuining."
15
+ ", remove and reboot before continuing."
16
16
 
17
17
  if feature(:instance).downstream
18
18
  base += " See https://access.redhat.com/solutions/5045841 for more details."
@@ -0,0 +1,28 @@
1
+ module Reports
2
+ class Compliance < ForemanMaintain::Report
3
+ metadata do
4
+ description 'Check if OpenSCAP is used'
5
+ end
6
+
7
+ def run
8
+ self.data = {}
9
+ mapping = {
10
+ 'policy':
11
+ 'foreman_openscap_policies',
12
+ 'policy_with_tailoring_file':
13
+ 'foreman_openscap_policies WHERE tailoring_file_id IS NOT NULL',
14
+ 'scap_contents':
15
+ "foreman_openscap_scap_contents",
16
+ 'non_default_scap_contents':
17
+ "foreman_openscap_scap_contents WHERE NOT original_filename LIKE 'ssg-rhel%-ds.xml'",
18
+ 'arf_report_last_year':
19
+ "reports WHERE type = 'ForemanOpenscap::ArfReport'
20
+ AND reported_at < NOW() - INTERVAL '1 year'",
21
+ }
22
+
23
+ mapping.each do |k, query|
24
+ data["compliance_#{k}_count"] = sql_count(query)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ module Reports
2
+ class ExternalAuthSource < ForemanMaintain::Report
3
+ metadata do
4
+ description 'Checks the use of External auth source'
5
+ end
6
+
7
+ # Do you use external auth source?
8
+ def run
9
+ self.data = {}
10
+ # nil means no user linked to external auth source ever logged in
11
+ data["last_login_on_through_external_auth_source_in_days"] = nil
12
+
13
+ sql = <<~SQL
14
+ SELECT users.* FROM users
15
+ INNER JOIN auth_sources ON (auth_sources.id = users.auth_source_id)
16
+ WHERE auth_sources.type = 'AuthSourceExternal' AND users.last_login_on IS NOT NULL
17
+ ORDER BY users.last_login_on DESC LIMIT 1
18
+ SQL
19
+ user = query(sql).first
20
+ if user
21
+ data["last_login_on_through_external_auth_source_in_days"] =
22
+ (Date.today - Date.parse(user['last_login_on'])).to_i
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,31 @@
1
+ module Reports
2
+ class Grouping < ForemanMaintain::Report
3
+ metadata do
4
+ description 'Check how resources are grouped'
5
+ end
6
+
7
+ def run
8
+ self.data = {}
9
+ data_field('host_collections_count') { sql_count('katello_host_collections') }
10
+
11
+ data_field('host_collections_with_limit_count') do
12
+ sql_count("katello_host_collections
13
+ WHERE unlimited_hosts = 'f'")
14
+ end
15
+
16
+ hostgroup = sql_count('hostgroups')
17
+ hostgroup_nest_level = sql_as_count(
18
+ "COALESCE(MAX((CHAR_LENGTH(ancestry) - CHAR_LENGTH(REPLACE(ancestry, '/', '')))) + 2, 1)",
19
+ 'hostgroups'
20
+ )
21
+ data['hostgroup_nesting'] = hostgroup_nest_level > 1
22
+ data['hostgroup_max_nesting_level'] = hostgroup.zero? ? 0 : hostgroup_nest_level
23
+
24
+ data_field('use_selectable_columns') { sql_count('table_preferences') > 0 }
25
+
26
+ if table_exists('config_groups')
27
+ data_field('config_group_count') { sql_count('config_groups') }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,11 @@
1
+ module Reports
2
+ class Instance < ForemanMaintain::Report
3
+ metadata do
4
+ description 'Report information about the instance itself'
5
+ end
6
+
7
+ def run
8
+ data_field('instance_uuid') { YAML.safe_load(sql_setting('instance_id')) }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,84 @@
1
+ module Reports
2
+ class Inventory < ForemanMaintain::Report
3
+ metadata do
4
+ description 'Facts about hosts and the rest of the inventory'
5
+ end
6
+
7
+ def run
8
+ merge_data('hosts_by_type_count') { hosts_by_type_count }
9
+ merge_data('hosts_by_os_count') { hosts_by_os_count }
10
+ merge_data('facts_by_type') { facts_by_type }
11
+ merge_data('audits') { audits }
12
+ merge_data('parameters_count') { parameters }
13
+ end
14
+
15
+ # Hosts
16
+ def hosts_by_type_count
17
+ query("select type, count(*) from hosts group by type").
18
+ to_h { |row| [(row['type'] || '').sub('Host::', ''), row['count'].to_i] }
19
+ end
20
+
21
+ # OS usage
22
+ def hosts_by_os_count
23
+ query(
24
+ <<-SQL
25
+ select max(operatingsystems.name) as os_name, count(*) as hosts_count
26
+ from hosts inner join operatingsystems on operatingsystem_id = operatingsystems.id
27
+ group by operatingsystem_id
28
+ SQL
29
+ ).
30
+ to_h { |row| [row['os_name'], row['hosts_count'].to_i] }
31
+ end
32
+
33
+ # Facts usage
34
+ def facts_by_type
35
+ query(
36
+ <<-SQL
37
+ select fact_names.type,
38
+ min(fact_values.updated_at) as min_update_time,
39
+ max(fact_values.updated_at) as max_update_time,
40
+ count(fact_values.id) as values_count
41
+ from fact_values inner join fact_names on fact_name_id = fact_names.id
42
+ group by fact_names.type
43
+ SQL
44
+ ).
45
+ to_h { |row| [row['type'].sub('FactName', ''), to_fact_hash(row)] }
46
+ end
47
+
48
+ # Audits
49
+ def audits
50
+ audits_query =
51
+ query(
52
+ <<-SQL
53
+ select count(*) as records_count,
54
+ min(created_at) as min_created_at,
55
+ max(created_at) as max_created_at
56
+ from audits
57
+ SQL
58
+ )
59
+ to_audits_record(audits_query.first)
60
+ end
61
+
62
+ # Parameters
63
+ def parameters
64
+ query("select type, count(*) from parameters group by type").
65
+ to_h { |row| [row['type'], row['count'].to_i] }
66
+ end
67
+
68
+ def to_fact_hash(row)
69
+ {
70
+ min_update_time: row['min_update_time'],
71
+ max_update_time: row['max_update_time'],
72
+ values_count: row['values_count'].to_i,
73
+ }
74
+ end
75
+
76
+ def to_audits_record(row)
77
+ {
78
+ records_count: row['records_count'].to_i,
79
+ min_created_at: row['min_created_at'],
80
+ max_created_at: row['max_created_at'],
81
+ }
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,27 @@
1
+ module Reports
2
+ class Kerberos < ForemanMaintain::Report
3
+ metadata do
4
+ description 'Checks the use of Kerberos'
5
+ tags :report
6
+ end
7
+
8
+ # Do you use kerberos?
9
+ # Do you use kerberos also for API authentication?
10
+ def run
11
+ authorize_login_delegation = sql_setting('authorize_login_delegation')
12
+ authorize_login_delegation_api = sql_setting('authorize_login_delegation_api')
13
+ oidc_issuer = sql_setting('oidc_issuer')
14
+ kerberos_result = authorize_login_delegation &&
15
+ YAML.load(authorize_login_delegation) == true &&
16
+ (oidc_issuer.nil? || YAML.load(oidc_issuer) == '')
17
+ kerberos_api_result = kerberos_result &&
18
+ authorize_login_delegation_api &&
19
+ YAML.load(authorize_login_delegation_api) == true
20
+
21
+ self.data = {
22
+ kerberos_use: !!kerberos_result,
23
+ kerberos_api_use: !!kerberos_api_result,
24
+ }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,73 @@
1
+ module Reports
2
+ class LDAPAuthSource < ForemanMaintain::Report
3
+ metadata do
4
+ description 'Checks the use of LDAP auth sources'
5
+ end
6
+
7
+ # Do you use FreeIPA LDAP auth source?
8
+ # Do you use AD LDAP auth source?
9
+ # Do you use POSIX LDAP auth source?
10
+ # Do you use netgroups schema?
11
+ # Do you disable automatic account creation on any LDAP auth source?
12
+ # Do you disable user group syncrhonization on any LDAP auth source?
13
+ # Do you have external user groups mapping?
14
+ def run
15
+ self.data = {}
16
+ data_field("external_user_group_mapping_count") { sql_count('external_usergroups') }
17
+ %w[free_ipa posix active_directory].reduce({}) do |_acc, flavor|
18
+ record_flavor_usage(flavor)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ # rubocop:disable Metrics/AbcSize
25
+ def record_flavor_usage(flavor)
26
+ flavored_query_base = query_base(flavor)
27
+ data["ldap_auth_source_#{flavor}_count"] = sql_count(flavored_query_base)
28
+
29
+ users = query(user_query(flavor))
30
+ data["users_authenticated_through_ldap_auth_source_#{flavor}"] = users.count
31
+
32
+ data["last_login_on_through_ldap_auth_source_#{flavor}_in_days"] = last_login(users)
33
+
34
+ data["ldap_auth_source_#{flavor}_with_net_groups_count"] =
35
+ sql_count("#{flavored_query_base} AND use_netgroups = true")
36
+
37
+ data["ldap_auth_source_#{flavor}_with_posix_groups_count"] =
38
+ sql_count("#{flavored_query_base} AND use_netgroups = false")
39
+
40
+ count = sql_count("#{flavored_query_base} AND onthefly_register = false")
41
+ data["ldap_auth_source_#{flavor}_with_account_creation_disabled_count"] = count
42
+
43
+ count = sql_count("#{flavored_query_base} AND usergroup_sync = false")
44
+ data["ldap_auth_source_#{flavor}_with_user_group_sync_disabled_count"] = count
45
+ end
46
+ # rubocop:enable Metrics/AbcSize
47
+
48
+ def last_login(users)
49
+ # nil means no user for a given LDAP type was found
50
+ if (user = users.first)
51
+ (Date.today - Date.parse(user['last_login_on'])).to_i
52
+ end
53
+ end
54
+
55
+ def query_base(flavor)
56
+ <<~SQL
57
+ auth_sources
58
+ WHERE auth_sources.type = 'AuthSourceLdap' AND auth_sources.server_type = '#{flavor}'
59
+ SQL
60
+ end
61
+
62
+ def user_query(flavor)
63
+ <<~SQL
64
+ SELECT users.* FROM users
65
+ INNER JOIN auth_sources ON (auth_sources.id = users.auth_source_id)
66
+ WHERE auth_sources.type = 'AuthSourceLdap'
67
+ AND auth_sources.server_type = '#{flavor}'
68
+ AND users.last_login_on IS NOT NULL
69
+ ORDER BY users.last_login_on DESC
70
+ SQL
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,16 @@
1
+ module Reports
2
+ class OIDC < ForemanMaintain::Report
3
+ metadata do
4
+ description 'Checks the use of Keycloak/OIDC'
5
+ end
6
+
7
+ # Do you use OIDC/keycloak?
8
+ def run
9
+ data_field('oidc_use') do
10
+ oidc_issuer = sql_setting('oidc_issuer') || ''
11
+ loaded = YAML.load(oidc_issuer)
12
+ loaded.is_a?(String) && loaded != ''
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,98 @@
1
+ module Reports
2
+ class Platform < ForemanMaintain::Report
3
+ metadata do
4
+ description 'Report about platform usages'
5
+ end
6
+
7
+ def run
8
+ general_fields
9
+ rbac_fields
10
+ settings_fields
11
+ bookmarks_fields
12
+ mail_notification_fields
13
+ user_groups_fields
14
+ end
15
+
16
+ def mail_notification_fields
17
+ # Mail notifications
18
+ # users_per_mail_notification =
19
+ # query(
20
+ # <<-SQL
21
+ # select
22
+ # max(mail_notifications.name) as notification_name,
23
+ # count(user_mail_notifications.user_id)
24
+ # from user_mail_notifications inner join mail_notifications
25
+ # on mail_notification_id = mail_notifications.id
26
+ # group by mail_notification_id
27
+ # SQL
28
+ # ).
29
+ # to_h { |row| [row['notification_name'], row['count'].to_i] }
30
+
31
+ data_field('user_mail_notifications_count') { sql_count('user_mail_notifications') }
32
+ end
33
+
34
+ def user_groups_fields
35
+ data_field('user_groups_count') { sql_count('usergroups') }
36
+ end
37
+
38
+ def general_fields
39
+ data_field('smart_proxies_count') { sql_count('smart_proxies') }
40
+ merge_data('smart_proxies_creation_date') do
41
+ query("select id, created_at from smart_proxies").
42
+ to_h { |row| [row['id'], row['created_at']] }
43
+ end
44
+ end
45
+
46
+ def rbac_fields
47
+ data_field('total_users_count') { sql_count('users') }
48
+ data_field('non_admin_users_count') { sql_count('users where admin = false') }
49
+
50
+ data_field('custom_roles_count') { sql_count('roles where origin = null') }
51
+
52
+ merge_data('taxonomies_counts') do
53
+ query("select type, count(*) from taxonomies group by type").
54
+ to_h { |row| [row['type'], row['count'].to_i] }
55
+ end
56
+ end
57
+
58
+ def settings_fields
59
+ data_field('modified_settings') do
60
+ query("select name from settings").
61
+ map { |setting_line| setting_line['name'] }.
62
+ join(',')
63
+ end
64
+ end
65
+
66
+ def bookmarks_fields
67
+ merge_data('bookmarks_by_public_by_type') do
68
+ query(
69
+ <<-SQL
70
+ select public, owner_type, count(*)
71
+ from bookmarks
72
+ group by public, owner_type
73
+ SQL
74
+ ).
75
+ to_h do |row|
76
+ [
77
+ "#{row['public'] ? 'public' : 'private'}#{flatten_separator}#{row['owner_type']}",
78
+ row['count'].to_i,
79
+ ]
80
+ end
81
+ end
82
+ # bookmarks_by_owner =
83
+ # query(
84
+ # <<-SQL
85
+ # select owner_type, owner_id, count(*)
86
+ # from bookmarks
87
+ # group by owner_type, owner_id
88
+ # SQL
89
+ # ).
90
+ # to_h do |row|
91
+ # [
92
+ # "#{row['owner_type']}#{flatten_separator}#{row['owner_id']}",
93
+ # row['count'].to_i,
94
+ # ]
95
+ # end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,109 @@
1
+ module Reports
2
+ class Provisioning < ForemanMaintain::Report
3
+ metadata do
4
+ description 'Provisioning facts about the system'
5
+ end
6
+
7
+ def run
8
+ data_field('managed_hosts_created_in_last_3_months') do
9
+ sql_count(
10
+ <<-SQL
11
+ hosts WHERE managed = true AND created_at >= current_date - interval '3 months'
12
+ SQL
13
+ )
14
+ end
15
+
16
+ compute_resources_fields
17
+ bare_metal_fields
18
+ templates_fields
19
+ end
20
+
21
+ def compute_resources_fields
22
+ hosts_by_compute_resources_type
23
+ hosts_by_compute_profile
24
+
25
+ merge_data('compute_resources_by_type') do
26
+ query(
27
+ <<-SQL
28
+ select type, count(*)
29
+ from compute_resources
30
+ group by type
31
+ SQL
32
+ ).
33
+ to_h { |row| [row['type'], row['count'].to_i] }
34
+ end
35
+ end
36
+
37
+ def hosts_by_compute_resources_type
38
+ merge_data('hosts_by_compute_resources_type') do
39
+ query(
40
+ <<-SQL
41
+ select compute_resources.type, count(hosts.id)
42
+ from hosts left outer join compute_resources on compute_resource_id = compute_resources.id
43
+ group by compute_resources.type
44
+ SQL
45
+ ).
46
+ to_h { |row| [row['type'] || 'baremetal', row['count'].to_i] }
47
+ end
48
+ end
49
+
50
+ def hosts_by_compute_profile
51
+ merge_data('hosts_by_compute_profile') do
52
+ query(
53
+ <<-SQL
54
+ select max(compute_profiles.name) as name, count(hosts.id)
55
+ from hosts left outer join compute_profiles on compute_profile_id = compute_profiles.id
56
+ group by compute_profile_id
57
+ SQL
58
+ ).
59
+ to_h { |row| [row['name'] || 'none', row['count'].to_i] }
60
+ end
61
+ end
62
+
63
+ def bare_metal_fields
64
+ nics_by_type_count
65
+ hosts_by_managed_count
66
+
67
+ data_field('discovery_rules_count') { sql_count('discovery_rules') }
68
+ end
69
+
70
+ def nics_by_type_count
71
+ merge_data('nics_by_type_count') do
72
+ query(
73
+ <<-SQL
74
+ select type, count(*)
75
+ from nics
76
+ group by type
77
+ SQL
78
+ ).
79
+ to_h { |row| [(row['type'] || 'none').sub('Nic::', ''), row['count'].to_i] }
80
+ end
81
+ end
82
+
83
+ def hosts_by_managed_count
84
+ merge_data('hosts_by_managed_count') do
85
+ query(
86
+ <<-SQL
87
+ select managed, count(*)
88
+ from hosts
89
+ group by managed
90
+ SQL
91
+ ).
92
+ to_h { |row| [row['managed'] == 't' ? 'managed' : 'unmanaged', row['count'].to_i] }
93
+ end
94
+ end
95
+
96
+ def templates_fields
97
+ merge_data('non_default_templates_per_type') do
98
+ query(
99
+ <<-SQL
100
+ select type, count(*) from templates
101
+ where templates.default = false
102
+ group by type
103
+ SQL
104
+ ).
105
+ to_h { |row| [row['type'], row['count'].to_i] }
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,68 @@
1
+ module Reports
2
+ class RBAC < ForemanMaintain::Report
3
+ metadata do
4
+ description 'Checks the RBAC use'
5
+ end
6
+
7
+ TAXONOMY_TYPES = %w[Organization Location].freeze
8
+
9
+ # How many users do you have in the system?
10
+ # How many non-admin users do you have?
11
+ # How many custom roles did you create and assigned to users?
12
+ def run
13
+ self.data = {}
14
+
15
+ data.merge!(user_counts)
16
+ data.merge!(custom_roles_counts)
17
+ data.merge!(taxonomy_counts)
18
+ data.merge!(taxonomy_ignore_type_uses)
19
+ end
20
+
21
+ def user_counts
22
+ users = sql_count("users" +
23
+ " INNER JOIN auth_sources ON auth_sources.id = users.auth_source_id" +
24
+ " WHERE auth_sources.name != 'Hidden'")
25
+
26
+ non_admin_users = sql_count("users" +
27
+ " LEFT OUTER JOIN cached_usergroup_members" +
28
+ " ON cached_usergroup_members.user_id = users.id" +
29
+ " LEFT OUTER JOIN usergroups ON usergroups.id = cached_usergroup_members.usergroup_id" +
30
+ " INNER JOIN auth_sources ON auth_sources.id = users.auth_source_id" +
31
+ " WHERE ((users.admin = FALSE OR users.admin IS NULL)" +
32
+ " AND (usergroups.admin = FALSE OR usergroups.admin IS NULL))" +
33
+ " AND auth_sources.name != 'Hidden'")
34
+
35
+ { 'users_count' => users, 'non_admin_users_count' => non_admin_users }
36
+ end
37
+
38
+ def custom_roles_counts
39
+ role_ids = query("SELECT id FROM roles WHERE roles.builtin != 2 AND roles.origin IS NULL")
40
+ roles_count = role_ids.size
41
+ role_ids = role_ids.flat_map(&:values)
42
+ assigned_count = if role_ids.empty?
43
+ 0
44
+ else
45
+ sql = "cached_user_roles" +
46
+ "WHERE cached_user_roles.role_id IN (#{role_ids.join(',')})"
47
+ sql_count(sql)
48
+ end
49
+
50
+ { 'custom_roles_count' => roles_count, 'assigned_custom_roles_count' => assigned_count }
51
+ end
52
+
53
+ def taxonomy_counts
54
+ TAXONOMY_TYPES.to_h do |t|
55
+ ["#{t.downcase}s_count", sql_count("taxonomies WHERE type = '#{t}'")]
56
+ end
57
+ end
58
+
59
+ def taxonomy_ignore_type_uses
60
+ TAXONOMY_TYPES.to_h do |t|
61
+ count = sql_count("taxonomies" +
62
+ " WHERE type = '#{t}'" +
63
+ " AND ignore_types IS NOT NULL")
64
+ ["#{t.downcase}_ignore_types_used", count.positive?]
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reports
4
+ class RecurringLogics < ForemanMaintain::Report
5
+ metadata do
6
+ description 'Check if recurring logics are used'
7
+ end
8
+
9
+ REX_TASK_GROUP_CTE = <<~SQL
10
+ WITH recurring_remote_execution_task_group_ids AS (
11
+ SELECT task_group_id
12
+ FROM foreman_tasks_task_groups as fttg
13
+ INNER JOIN foreman_tasks_task_group_members AS fttgm
14
+ ON fttgm.task_group_id = fttg.id
15
+ INNER JOIN foreman_tasks_tasks AS ftt
16
+ ON fttgm.task_id = ftt.id
17
+ WHERE
18
+ fttg.type = 'ForemanTasks::TaskGroups::RecurringLogicTaskGroup'
19
+ AND ftt.label = 'Actions::RemoteExecution::RunHostsJob'
20
+ ), indefinite_rex_recurring_logics AS (
21
+ SELECT * FROM foreman_tasks_recurring_logics AS ftrl
22
+ WHERE ftrl.task_group_id IN (SELECT task_group_id FROM recurring_remote_execution_task_group_ids)
23
+ AND (ftrl.end_time IS NULL OR ftrl.max_iteration IS NULL)
24
+ )
25
+ SQL
26
+
27
+ def run
28
+ self.data = {}
29
+ data['recurring_logics_indefinite_rex_count'] = sql_count('indefinite_rex_recurring_logics')
30
+ data['recurring_logics_indefinite_rex_ansible_count'] =
31
+ sql_count("indefinite_rex_recurring_logics WHERE purpose LIKE 'ansible-%'")
32
+ end
33
+
34
+ private
35
+
36
+ def sql_count(query)
37
+ super(query, cte: REX_TASK_GROUP_CTE)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,21 @@
1
+ # copy the file and add the .rb suffix
2
+ module Checks
3
+ module Report
4
+ class Template < ForemanMaintain::Report
5
+ metadata do
6
+ description 'One sentence description'
7
+ end
8
+
9
+ def run
10
+ data_field('some_value') { 'hello' }
11
+ merge_data('key_prefix') do
12
+ {
13
+ 'key1': 'value1',
14
+ 'key2': 'value2',
15
+ 'nested': { 'another_key': 'another_value', 'more': 'more_value' }
16
+ }
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module Reports
2
+ class Virtwho < ForemanMaintain::Report
3
+ metadata do
4
+ description 'Check if virt-who is being used and what hypervisor types are present'
5
+ end
6
+
7
+ HYPERVISOR_TYPES = %w[ahv esx fakevirt hyperv kubevirt libvirtd].freeze
8
+
9
+ def run
10
+ self.data = {}
11
+ data_field('foreman_virt_who_configure_configurations_count') do
12
+ sql_count('foreman_virt_who_configure_configs')
13
+ end
14
+
15
+ HYPERVISOR_TYPES.each do |type|
16
+ data["foreman_virt_who_configure_#{type}_count"] =
17
+ sql_count("foreman_virt_who_configure_configs WHERE hypervisor_type = '#{type}'")
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ module Reports
2
+ class Vmware < ForemanMaintain::Report
3
+ metadata do
4
+ description 'Check if vmware compute resource is used'
5
+ end
6
+
7
+ def run
8
+ data_field('compute_resource_vmware_count') do
9
+ sql_count("compute_resources WHERE type = 'Foreman::Model::Vmware'")
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ module ForemanMaintain::Scenarios
2
+ module Report
3
+ class Generate < ForemanMaintain::Scenario::FilteredScenario
4
+ metadata do
5
+ description 'Generate the usage reports'
6
+ tags :report
7
+ label :generate_report
8
+ manual_detection
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,25 @@
1
+ module ForemanMaintain
2
+ module Cli
3
+ class ReportCommand < Base
4
+ extend Concerns::Finders
5
+
6
+ option '--output', 'FILE', 'Output the generate report into FILE'
7
+ subcommand 'generate', 'Generates the usage reports' do
8
+ def execute
9
+ scenario = run_scenario(Scenarios::Report::Generate.new({}, [:reports])).first
10
+
11
+ # description can be used too
12
+ report_data = scenario.steps.map(&:data).compact.reduce(&:merge).transform_keys(&:to_s)
13
+ report_data['version'] = 1
14
+ yaml = report_data.to_yaml
15
+ if @output
16
+ File.write(@output, yaml)
17
+ else
18
+ puts yaml
19
+ end
20
+ exit runner.exit_code
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -11,6 +11,7 @@ require 'foreman_maintain/cli/restore_command'
11
11
  require 'foreman_maintain/cli/maintenance_mode_command'
12
12
  require 'foreman_maintain/cli/packages_command'
13
13
  require 'foreman_maintain/cli/plugin_command'
14
+ require 'foreman_maintain/cli/report_command'
14
15
  require 'foreman_maintain/cli/self_upgrade_command'
15
16
  require 'foreman_maintain/cli/update_command'
16
17
 
@@ -33,6 +34,7 @@ module ForemanMaintain
33
34
  subcommand 'self-upgrade', 'Perform major version self upgrade', SelfUpgradeCommand
34
35
  subcommand 'maintenance-mode', 'Control maintenance-mode for application',
35
36
  MaintenanceModeCommand
37
+ subcommand 'report', 'Generate usage report', ReportCommand
36
38
 
37
39
  def run(*arguments)
38
40
  logger.info("Running foreman-maintain command with arguments #{arguments.inspect}")
@@ -41,6 +41,14 @@ module ForemanMaintain
41
41
  filter(@available_checks, filter_conditions)
42
42
  end
43
43
 
44
+ def available_reports(filter_conditions = nil)
45
+ unless @available_reports
46
+ ensure_features_detected
47
+ @available_reports = find_present_classes(Report)
48
+ end
49
+ filter(@available_reports, filter_conditions)
50
+ end
51
+
44
52
  def available_procedures(filter_conditions = nil)
45
53
  unless @available_procedures
46
54
  ensure_features_detected
@@ -0,0 +1,79 @@
1
+ module ForemanMaintain
2
+ class Report < Executable
3
+ include Concerns::Logger
4
+ include Concerns::SystemHelpers
5
+ include Concerns::Metadata
6
+ include Concerns::Finders
7
+
8
+ attr_accessor :data
9
+
10
+ def query(sql)
11
+ feature(:foreman_database).query(sql)
12
+ end
13
+
14
+ def sql_count(sql, column: '*', cte: '')
15
+ sql_as_count("COUNT(#{column})", sql, cte: cte)
16
+ end
17
+
18
+ def sql_as_count(selection, sql, cte: '')
19
+ query = "#{cte} SELECT #{selection} AS COUNT FROM #{sql}"
20
+ feature(:foreman_database).query(query).first['count'].to_i
21
+ rescue StandardError
22
+ nil
23
+ end
24
+
25
+ def sql_setting(name)
26
+ sql = "SELECT value FROM settings WHERE name = '#{name}'"
27
+ result = feature(:foreman_database).query(sql).first
28
+ (result || {})['value']
29
+ end
30
+
31
+ def flatten(hash, prefix = '')
32
+ hash.each_with_object({}) do |(key, value), result|
33
+ new_key = "#{prefix}#{prefix.empty? ? '' : flatten_separator}#{key}"
34
+ if value.is_a? Hash
35
+ result.merge!(flatten(value, new_key))
36
+ else
37
+ result[new_key] = value
38
+ end
39
+ end
40
+ end
41
+
42
+ def table_exists(table)
43
+ sql_count("information_schema.tables WHERE table_name = '#{table}'").positive?
44
+ end
45
+
46
+ def data_field(field_name)
47
+ self.data ||= {}
48
+ value = yield
49
+ self.data[field_name] = value
50
+ rescue StandardError
51
+ nil
52
+ end
53
+
54
+ def merge_data(prefix)
55
+ self.data ||= {}
56
+ data_hash = yield
57
+ self.data.merge!(flatten(data_hash, prefix))
58
+ rescue StandardError
59
+ nil
60
+ end
61
+
62
+ def run
63
+ raise NoMethodError, 'method not implemented on abstract report classes'
64
+ end
65
+
66
+ # internal method called by executor
67
+ def __run__(execution)
68
+ super
69
+ rescue Error::Fail => e
70
+ set_fail(e.message)
71
+ rescue StandardError => e
72
+ set_warn(e.message)
73
+ end
74
+
75
+ def flatten_separator
76
+ '|'
77
+ end
78
+ end
79
+ end
@@ -23,7 +23,9 @@ module ForemanMaintain
23
23
  @definition_kinds = definition_kinds
24
24
  @steps = []
25
25
  @steps += checks(filter) if definition_kinds.include?(:check)
26
+ @steps += reports(filter) if definition_kinds.include?(:reports)
26
27
  @steps += procedures(filter) if definition_kinds.include?(:procedure)
28
+
27
29
  @steps = DependencyGraph.sort(@steps)
28
30
  end
29
31
 
@@ -57,6 +59,10 @@ module ForemanMaintain
57
59
  ForemanMaintain.available_checks(filter).map(&:ensure_instance)
58
60
  end
59
61
 
62
+ def reports(filter)
63
+ ForemanMaintain.available_reports(filter).map(&:ensure_instance)
64
+ end
65
+
60
66
  def procedures(filter)
61
67
  ForemanMaintain.available_procedures(filter).map(&:ensure_instance)
62
68
  end
@@ -39,9 +39,8 @@ module ForemanMaintain::Utils
39
39
  end
40
40
 
41
41
  def restart
42
- command_name = ForemanMaintain.command_name
43
42
  db_status(<<~MSG
44
- Remote databases are not managed by #{command_name} and therefore was not restarted.
43
+ Remote databases are not managed and therefore no restart was performed.
45
44
  MSG
46
45
  )
47
46
  end
@@ -1,3 +1,3 @@
1
1
  module ForemanMaintain
2
- VERSION = '1.9.2'.freeze
2
+ VERSION = '1.10.0'.freeze
3
3
  end
@@ -36,6 +36,7 @@ module ForemanMaintain
36
36
  require 'foreman_maintain/feature'
37
37
  require 'foreman_maintain/executable'
38
38
  require 'foreman_maintain/check'
39
+ require 'foreman_maintain/report'
39
40
  require 'foreman_maintain/procedure'
40
41
  require 'foreman_maintain/scenario'
41
42
  require 'foreman_maintain/runner'
@@ -131,6 +132,10 @@ module ForemanMaintain
131
132
  detector.available_checks(*args)
132
133
  end
133
134
 
135
+ def available_reports(*args)
136
+ detector.available_reports(*args)
137
+ end
138
+
134
139
  def available_procedures(*args)
135
140
  detector.available_procedures(*args)
136
141
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_maintain
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.2
4
+ version: 1.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Nečas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-01-08 00:00:00.000000000 Z
11
+ date: 2025-01-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: clamp
@@ -307,11 +307,27 @@ files:
307
307
  - definitions/procedures/service/stop.rb
308
308
  - definitions/procedures/sync_plans/disable.rb
309
309
  - definitions/procedures/sync_plans/enable.rb
310
+ - definitions/reports/compliance.rb
311
+ - definitions/reports/external_auth_source.rb
312
+ - definitions/reports/grouping.rb
313
+ - definitions/reports/instance.rb
314
+ - definitions/reports/inventory.rb
315
+ - definitions/reports/kerberos.rb
316
+ - definitions/reports/ldap_auth_source.rb
317
+ - definitions/reports/oidc_usage.rb
318
+ - definitions/reports/platform.rb
319
+ - definitions/reports/provisioning.rb
320
+ - definitions/reports/rbac.rb
321
+ - definitions/reports/recurring_logics.rb
322
+ - definitions/reports/template
323
+ - definitions/reports/virt_who.rb
324
+ - definitions/reports/vmware.rb
310
325
  - definitions/scenarios/backup.rb
311
326
  - definitions/scenarios/foreman_upgrade.rb
312
327
  - definitions/scenarios/maintenance_mode.rb
313
328
  - definitions/scenarios/packages.rb
314
329
  - definitions/scenarios/puppet.rb
330
+ - definitions/scenarios/report.rb
315
331
  - definitions/scenarios/restore.rb
316
332
  - definitions/scenarios/satellite_upgrade.rb
317
333
  - definitions/scenarios/self_upgrade.rb
@@ -337,6 +353,7 @@ files:
337
353
  - lib/foreman_maintain/cli/maintenance_mode_command.rb
338
354
  - lib/foreman_maintain/cli/packages_command.rb
339
355
  - lib/foreman_maintain/cli/plugin_command.rb
356
+ - lib/foreman_maintain/cli/report_command.rb
340
357
  - lib/foreman_maintain/cli/restore_command.rb
341
358
  - lib/foreman_maintain/cli/self_upgrade_command.rb
342
359
  - lib/foreman_maintain/cli/service_command.rb
@@ -378,6 +395,7 @@ files:
378
395
  - lib/foreman_maintain/package_manager/dnf.rb
379
396
  - lib/foreman_maintain/param.rb
380
397
  - lib/foreman_maintain/procedure.rb
398
+ - lib/foreman_maintain/report.rb
381
399
  - lib/foreman_maintain/reporter.rb
382
400
  - lib/foreman_maintain/reporter/cli_reporter.rb
383
401
  - lib/foreman_maintain/repository_manager.rb