Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add usage report command #851

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
23 changes: 23 additions & 0 deletions definitions/reports/external_auth_source.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module Checks
module Report
class ExternalAuthSource < ForemanMaintain::Report
metadata do
description 'Checks the use of External auth source'
end

# Do you use external auth source?
def run
result = {}
# nil means no user linked to external auth source ever logged in
result["last_login_on_through_external_auth_source_in_days"] = nil

users = feature(:foreman_database).query("SELECT users.* FROM users INNER JOIN auth_sources ON (auth_sources.id = users.auth_source_id) WHERE auth_sources.type = 'AuthSourceExternal' AND users.last_login_on IS NOT NULL ORDER BY users.last_login_on DESC")

Check failure on line 14 in definitions/reports/external_auth_source.rb

View workflow job for this annotation

GitHub Actions / rubocop / Rubocop

Layout/LineLength: Line is too long. [262/100]
if (user = users.first)
result["last_login_on_through_external_auth_source_in_days"] = (Date.today - Date.parse(user['last_login_on'])).to_i

Check failure on line 16 in definitions/reports/external_auth_source.rb

View workflow job for this annotation

GitHub Actions / rubocop / Rubocop

Layout/LineLength: Line is too long. [126/100]
end

self.data = result
end
end
end
end
30 changes: 30 additions & 0 deletions definitions/reports/grouping.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module Checks
module Report
class Grouping < ForemanMaintain::Report
metadata do
description 'Check how resources are grouped'
end

def run
collection_count = sql_count('SELECT COUNT(*) FROM katello_host_collections')
collection_count_with_limit = sql_count("SELECT COUNT(*) FROM katello_host_collections
WHERE unlimited_hosts = 'f'")
hostgroup = sql_count('SELECT COUNT(*) FROM hostgroups')
sql = <<~SQL
SELECT COALESCE(MAX((CHAR_LENGTH(ancestry) - CHAR_LENGTH(REPLACE(ancestry, '/', '')))) + 2, 1) AS count
FROM hostgroups
SQL
hostgroup_nest_level = sql_count(sql)
table_preference_count = sql_count('SELECT COUNT(*) FROM table_preferences')
config_group_count = sql_count('SELECT COUNT(*) FROM config_groups')
self.data = { 'host_collections_count': collection_count,
'host_collections_count_with_limit': collection_count_with_limit,
'hostgroup_count': hostgroup,
'hostgroup_nesting': hostgroup_nest_level > 1,
'hostgroup_max_nesting_level': hostgroup.zero? ? 0 : hostgroup_nest_level,
'use_selectable_columns': table_preference_count > 0,
'config_group_count': config_group_count }
end
end
end
end
26 changes: 26 additions & 0 deletions definitions/reports/kerberos.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module Checks
module Report
class Kerberos < ForemanMaintain::Report
metadata do
description 'Checks the use of Kerberos'
tags :report
end

# Do you use kerberos?
# Do you use kerberos also for API authentication?
def run

Check failure on line 11 in definitions/reports/kerberos.rb

View workflow job for this annotation

GitHub Actions / rubocop / Rubocop

Metrics/AbcSize: Assignment Branch Condition size for run is too high. [<6, 21, 8> 23.26/22]
authorize_login_delegation = feature(:foreman_database).query("SELECT value FROM settings WHERE name = 'authorize_login_delegation'").first

Check failure on line 12 in definitions/reports/kerberos.rb

View workflow job for this annotation

GitHub Actions / rubocop / Rubocop

Layout/LineLength: Line is too long. [147/100]
authorize_login_delegation_api = feature(:foreman_database).query("SELECT value FROM settings WHERE name = 'authorize_login_delegation_api'").first

Check failure on line 13 in definitions/reports/kerberos.rb

View workflow job for this annotation

GitHub Actions / rubocop / Rubocop

Layout/LineLength: Line is too long. [155/100]
oidc_issuer = feature(:foreman_database).query("SELECT value FROM settings WHERE name = 'oidc_issuer'").first

Check failure on line 14 in definitions/reports/kerberos.rb

View workflow job for this annotation

GitHub Actions / rubocop / Rubocop

Layout/LineLength: Line is too long. [117/100]
kerberos_result = authorize_login_delegation && YAML.load(authorize_login_delegation['value']) == true &&

Check failure on line 15 in definitions/reports/kerberos.rb

View workflow job for this annotation

GitHub Actions / rubocop / Rubocop

Layout/LineLength: Line is too long. [113/100]
(oidc_issuer.nil? || YAML.load(oidc_issuer['value']) == '')

Check failure on line 16 in definitions/reports/kerberos.rb

View workflow job for this annotation

GitHub Actions / rubocop / Rubocop

Layout/MultilineOperationIndentation: Align the operands of an expression in an assignment spanning multiple lines.
kerberos_api_result = kerberos_result && authorize_login_delegation_api && YAML.load(authorize_login_delegation_api['value']) == true

Check failure on line 17 in definitions/reports/kerberos.rb

View workflow job for this annotation

GitHub Actions / rubocop / Rubocop

Layout/LineLength: Line is too long. [141/100]

self.data = {
kerberos_use: !!kerberos_result,
kerberos_api_use: !!kerberos_api_result

Check failure on line 21 in definitions/reports/kerberos.rb

View workflow job for this annotation

GitHub Actions / rubocop / Rubocop

Style/TrailingCommaInHashLiteral: Put a comma after the last item of a multiline hash.
}
end
end
end
end
52 changes: 52 additions & 0 deletions definitions/reports/ldap_auth_source.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
module Checks
module Report
class LDAPAuthSource < ForemanMaintain::Report
metadata do
description 'Checks the use of LDAP auth sources'
end

# Do you use FreeIPA LDAP auth source?
# Do you use AD LDAP auth source?
# Do you use POSIX LDAP auth source?
# Do you use netgroups schema?
# Do you disable automatic account creation on any LDAP auth source?
# Do you disable user group syncrhonization on any LDAP auth source?
# Do you have external user groups mapping?
def run
result = {}

%w(free_ipa posix active_directory).each do |flavor|
count = sql_count("SELECT COUNT(*) FROM auth_sources WHERE auth_sources.type = 'AuthSourceLdap' AND server_type = '#{flavor}'")
result["ldap_auth_source_#{flavor}_count"] = count

users = feature(:foreman_database).query("SELECT users.* FROM users INNER JOIN auth_sources ON (auth_sources.id = users.auth_source_id) WHERE auth_sources.type = 'AuthSourceLdap' AND auth_sources.server_type = '#{flavor}' AND users.last_login_on IS NOT NULL ORDER BY users.last_login_on DESC")
result["users_authenticated_through_ldap_auth_source_#{flavor}"] = users.count
# nil means no user for a given LDAP type was found
if (user = users.first)
result["last_login_on_through_ldap_auth_source_#{flavor}_in_days"] = (Date.today - Date.parse(user['last_login_on'])).to_i
else
result["last_login_on_through_ldap_auth_source_#{flavor}_in_days"] = nil
end

count = sql_count("SELECT COUNT(*) FROM auth_sources WHERE auth_sources.type = 'AuthSourceLdap' AND auth_sources.server_type = '#{flavor}' AND use_netgroups = true")
result["ldap_auth_source_#{flavor}_with_net_groups_count"] = count

count = sql_count("SELECT COUNT(*) FROM auth_sources WHERE auth_sources.type = 'AuthSourceLdap' AND auth_sources.server_type = '#{flavor}' AND use_netgroups = false")
result["ldap_auth_source_#{flavor}_with_posix_groups_count"] = count

count = sql_count("SELECT COUNT(*) FROM auth_sources WHERE auth_sources.type = 'AuthSourceLdap' AND auth_sources.server_type = '#{flavor}' AND onthefly_register = false")
result["ldap_auth_source_#{flavor}_with_account_creation_disabled_count"] = count

count = sql_count("SELECT COUNT(*) FROM auth_sources WHERE auth_sources.type = 'AuthSourceLdap' AND auth_sources.server_type = '#{flavor}' AND usergroup_sync = false")
result["ldap_auth_source_#{flavor}_with_user_group_sync_disabled_count"] = count
end


count = feature(:foreman_database).query("SELECT COUNT(*) FROM external_usergroups")
result["external_user_group_mapping_count"] = count.first['count'].to_i

self.data = result
end
end
end
end
17 changes: 17 additions & 0 deletions definitions/reports/oidc_usage.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module Checks
module Report
class OIDC < ForemanMaintain::Report
metadata do
description 'Checks the use of Keycloak/OIDC'
end

# Do you use OIDC/keycloak?
def run
oidc_issuer = feature(:foreman_database).query("SELECT value FROM settings WHERE name = 'oidc_issuer'").first
result = (oidc_issuer && YAML.load(oidc_issuer['value']).is_a?(String) && YAML.load(oidc_issuer['value']) != '')

self.data = { oidc_use: !!result }
end
end
end
end
16 changes: 16 additions & 0 deletions definitions/reports/provisioning_usage.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module Checks
module Report
class Provisioning < ForemanMaintain::Report
metadata do
description 'Count hosts that have been provisioned in the last 3 months.'
end

def run
count = sql_count("SELECT COUNT(*) FROM hosts WHERE managed = true AND created_at >= current_date - interval '3 months'")
result = count

self.data = { managed_hosts_created_in_last_3_months: result }
end
end
end
end
40 changes: 40 additions & 0 deletions definitions/reports/recurring_logics.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module Checks
module Report
class RecurringLogics < ForemanMaintain::Report
metadata do
description 'Check if recurring logics are used'
end

REX_TASK_GROUP_CTE = <<~SQL
WITH recurring_remote_execution_task_group_ids AS (
SELECT task_group_id
FROM foreman_tasks_task_groups as fttg
INNER JOIN foreman_tasks_task_group_members AS fttgm
ON fttgm.task_group_id = fttg.id
INNER JOIN foreman_tasks_tasks AS ftt
ON fttgm.task_id = ftt.id
WHERE
fttg.type = 'ForemanTasks::TaskGroups::RecurringLogicTaskGroup'
AND ftt.label = 'Actions::RemoteExecution::RunHostsJob'
), indefinite_rex_recurring_logics AS (
SELECT * FROM foreman_tasks_recurring_logics AS ftrl
WHERE ftrl.task_group_id IN (SELECT task_group_id FROM recurring_remote_execution_task_group_ids)
AND (ftrl.end_time IS NULL OR ftrl.max_iteration IS NULL)
)
SQL

def run
count = sql_count('SELECT COUNT(*) FROM indefinite_rex_recurring_logics')
ansible_count = sql_count("SELECT COUNT(*) FROM indefinite_rex_recurring_logics WHERE purpose LIKE 'ansible-%'")
self.data = { "recurring_logics_indefinite_rex_count": count,
"recurring_logics_indefinite_rex_ansible_count": ansible_count }
end

private

def sql_count(query)
super(REX_TASK_GROUP_CTE + query)
end
end
end
end
20 changes: 20 additions & 0 deletions definitions/reports/template
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# copy the file and add the .rb suffix
module Checks
module Report
class Template < ForemanMaintain::Report
metadata do
description 'One sentence description'
end

def run
# make sure to populate data with the hash, that will be merged into the result set
self.data = { template: 5 }

# SQL example
# count = feature(:foreman_database).query("SELECT COUNT(*) FROM hosts WHERE managed = true AND created_at >= current_date - interval '3 months'")
# result = count.first['count'].to_i
# self.data = result
end
end
end
end
14 changes: 14 additions & 0 deletions definitions/reports/vmware.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module Checks
module Report
class Vmware < ForemanMaintain::Report
metadata do
description 'Check if vmware compute resource is used'
end

def run
count = sql_count("SELECT COUNT(*) FROM compute_resources WHERE type = 'Foreman::Model::Vmware'")
self.data = { "compute_resource_vmware_count": count }
end
end
end
end
12 changes: 12 additions & 0 deletions definitions/scenarios/report.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module ForemanMaintain::Scenarios
module Report
class Generate < ForemanMaintain::Scenario::FilteredScenario
metadata do
description 'Generate the usage reports'
tags :report
label :generate_report
manual_detection
end
end
end
end
5 changes: 5 additions & 0 deletions lib/foreman_maintain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ module ForemanMaintain
require 'foreman_maintain/feature'
require 'foreman_maintain/executable'
require 'foreman_maintain/check'
require 'foreman_maintain/report'
require 'foreman_maintain/procedure'
require 'foreman_maintain/scenario'
require 'foreman_maintain/runner'
Expand Down Expand Up @@ -130,6 +131,10 @@ def available_checks(*args)
detector.available_checks(*args)
end

def available_reports(*args)
detector.available_reports(*args)
end

def available_procedures(*args)
detector.available_procedures(*args)
end
Expand Down
2 changes: 2 additions & 0 deletions lib/foreman_maintain/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
require 'foreman_maintain/cli/maintenance_mode_command'
require 'foreman_maintain/cli/packages_command'
require 'foreman_maintain/cli/plugin_command'
require 'foreman_maintain/cli/report_command'
require 'foreman_maintain/cli/self_upgrade_command'
require 'foreman_maintain/cli/update_command'

Expand All @@ -33,6 +34,7 @@ class MainCommand < Base
subcommand 'self-upgrade', 'Perform major version self upgrade', SelfUpgradeCommand
subcommand 'maintenance-mode', 'Control maintenance-mode for application',
MaintenanceModeCommand
subcommand 'report', 'Generate the usage reports', ReportCommand

def run(*arguments)
logger.info("Running foreman-maintain command with arguments #{arguments.inspect}")
Expand Down
20 changes: 20 additions & 0 deletions lib/foreman_maintain/cli/report_command.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module ForemanMaintain
module Cli
class ReportCommand < Base
extend Concerns::Finders

subcommand 'generate', 'Generates the usage reports' do
def execute
scenario = run_scenario(Scenarios::Report::Generate.new({}, [ :reports ])).first

# description can be used too
report_data = scenario.steps.map(&:data).reduce(&:merge).transform_keys(&:to_s)
#require 'pry'
#binding.pry
puts report_data.to_yaml
exit runner.exit_code
end
end
end
end
end
8 changes: 8 additions & 0 deletions lib/foreman_maintain/detector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ def available_checks(filter_conditions = nil)
filter(@available_checks, filter_conditions)
end

def available_reports(filter_conditions = nil)
unless @available_reports
ensure_features_detected
@available_reports = find_present_classes(Report)
end
filter(@available_reports, filter_conditions)
end

def available_procedures(filter_conditions = nil)
unless @available_procedures
ensure_features_detected
Expand Down
27 changes: 27 additions & 0 deletions lib/foreman_maintain/report.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module ForemanMaintain
class Report < Executable
include Concerns::Logger
include Concerns::SystemHelpers
include Concerns::Metadata
include Concerns::Finders

attr_accessor :data

def sql_count(sql)
feature(:foreman_database).query(sql).first['count'].to_i
end

def run
raise NotImplementedError
end

# internal method called by executor
def __run__(execution)
super
rescue Error::Fail => e
set_fail(e.message)
rescue Error::Warn => e
set_warn(e.message)
end
end
end
6 changes: 6 additions & 0 deletions lib/foreman_maintain/scenario.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ def initialize(filter, definition_kinds = [:check])
@definition_kinds = definition_kinds
@steps = []
@steps += checks(filter) if definition_kinds.include?(:check)
@steps += reports(filter) if definition_kinds.include?(:reports)
@steps += procedures(filter) if definition_kinds.include?(:procedure)

@steps = DependencyGraph.sort(@steps)
end

Expand Down Expand Up @@ -57,6 +59,10 @@ def checks(filter)
ForemanMaintain.available_checks(filter).map(&:ensure_instance)
end

def reports(filter)
ForemanMaintain.available_reports(filter).map(&:ensure_instance)
end

def procedures(filter)
ForemanMaintain.available_procedures(filter).map(&:ensure_instance)
end
Expand Down
1 change: 1 addition & 0 deletions test/lib/cli_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ module ForemanMaintain
plugin Manage optional plugins on your server
self-upgrade Perform major version self upgrade
maintenance-mode Control maintenance-mode for application
report Generate the usage reports

Options:
-h, --help print help
Expand Down
Loading
Loading