Skip to content

Commit

Permalink
Fixes #22739, #21740 - Maintain hammer configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
mbacovsky authored and iNecas committed Apr 12, 2018
1 parent c83f540 commit 8b3e44a
Show file tree
Hide file tree
Showing 26 changed files with 450 additions and 239 deletions.
5 changes: 5 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,8 @@ Style/RedundantReturn:

Style/FrozenStringLiteralComment:
Enabled: false

Style/DoubleNegation:
Description: 'Checks for uses of double negation (!!).'
StyleGuide: '#no-bang-bang'
Enabled: false
24 changes: 14 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,24 +297,28 @@ end
### Hammer

In some cases, it's useful to be able to use the hammer as part of check/fix procedures.
The easiest way to achieve this is to include `ForemanMaintain::Concerns::Hammer` module:
It is as simple as:

```ruby
include ForemanMaintain::Concerns::Hammer

def run
hammer('task resume')
feature(:hammer).run('task resume')
end
```

We expect the credentials for the hammer commands to to be stored inside the hammer settings:
Before executing the command the feature checks if it has valid hammer configuration to run the command.
Foreman maintain always use the 'admin' account to run the commands. The password is taken either form
the Hammer config or installer answer files or asked from the user interactively (in this order).
The valid credentials are stored and reused next time if still valid.

Usually we want to do the user interaction at the beginning of our scenario.
The easiest way to achieve this is to include `ForemanMaintain::Concerns::Hammer` module:

```ruby
include ForemanMaintain::Concerns::Hammer
```
# ~/.hammer/cli.modules.d/foreman.yml
:foreman:
:username: 'admin'
:password: 'changeme'
```

which adds `Procedures::HammerSetup` as a preparation step to your metadata. We are adding this
to all procedures and checks automatically.

## Metadata

Expand Down
2 changes: 1 addition & 1 deletion definitions/features/foreman_tasks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def condition(state)
end

def resume_task_using_hammer
hammer('task resume')
feature(:hammer).run('task resume')
end

def fetch_tasks_status(state, spinner)
Expand Down
162 changes: 159 additions & 3 deletions definitions/features/hammer.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
require 'uri'

class Features::Hammer < ForemanMaintain::Feature
attr_reader :configuration, :config_files

metadata do
label :hammer
confine do
find_package('rubygem-hammer_cli') || find_package('tfm-rubygem-hammer_cli')
end
end

SERVICES_MAPPING = {
Expand All @@ -11,6 +18,32 @@ class Features::Hammer < ForemanMaintain::Feature
'foreman_tasks' => %w[foreman-tasks]
}.freeze

def initialize
@configuration = { :foreman => {} }
@config_files = []
@ready = nil
load_configuration
end

def config_directories
[
'~/.hammer/',
'/etc/hammer/'
]
end

def setup_admin_access
return true if check_connection
logger.info('Hammer setup is not valid. Fixing configuration.')
custom_config = { :foreman => { :username => 'admin' } }
custom_config = on_invalid_host(custom_config)
custom_config = on_missing_password(custom_config) # get password from answers
custom_config = on_invalid_password(custom_config) # get password from answers
ask_password_and_check(custom_config) unless ready?
config_error unless ready?
ready?
end

def hammer_ping_cmd
cmd_output = exec_hammer_cmd('--output json ping', true)
return init_result_obj(false, cmd_output) if cmd_output.is_a?(String)
Expand All @@ -21,6 +54,90 @@ def hammer_ping_cmd
init_result_obj(false, msg_to_show, services)
end

def ready?
setup_admin_access if @ready.nil?
@ready
end

def check_connection
@ready = _check_connection
end

def server_uri
# FIXME: How does this run on proxy?
"https://#{hostname}/"
end

def custom_config_file
fm_config_dir = File.dirname(ForemanMaintain.config_file)
File.join(fm_config_dir, 'foreman-maintain-hammer.yml')
end

def command_base
if File.exist?(custom_config_file)
%(RUBYOPT='-W0' LANG=en_US.utf-8 hammer -c "#{custom_config_file}" --interactive=no)
else
%(RUBYOPT='-W0' LANG=en_US.utf-8 hammer --interactive=no)
end
end

# Run a hammer command, examples:
# run('host list')
def run(args)
setup_admin_access
execute("#{command_base} #{args}")
end

private

# rubocop:disable Metrics/AbcSize
def on_invalid_host(custom_config)
hammer_host = URI.parse(configuration[:foreman][:host]).host if configuration[:foreman][:host]
if hammer_host != hostname
logger.info("Matching hostname was not found in hammer configs. Using #{server_uri}")
custom_config[:foreman][:host] = server_uri
end
custom_config
end

def on_invalid_password(custom_config)
if !ready? && custom_config[:foreman][:password].nil?
msg = 'Invalid admin password was found in hammer configs. Looking into installer answers'
logger.info(msg)
custom_config[:foreman][:password] = password_from_answers
save_config_and_check(custom_config)
end
custom_config
end

def ask_password_and_check(custom_config)
custom_config[:foreman][:password] = ask('Hammer admin password:', :password => true)
save_config_and_check(custom_config)
custom_config
end

def config_error
raise ForemanMaintain::HammerConfigurationError, 'Hammer configuration failed: '\
"Is the admin password correct? (it was stored in #{custom_config_file})" \
'Is the server down?'
end

def on_missing_password(custom_config)
if admin_password_missing?
msg = 'Admin password was not found in hammer configs. Looking into installer answers'
logger.info(msg)
custom_config[:foreman][:password] = password_from_answers
end
save_config_and_check(custom_config)
custom_config
end

def admin_password_missing?
configuration[:foreman][:password].nil? ||
configuration[:foreman][:password].empty? ||
configuration[:foreman][:username] != 'admin'
end

def find_resources_which_failed(hammer_ping_output)
resources_failed = []
hammer_ping_output.each do |resource, resp_obj|
Expand All @@ -29,8 +146,6 @@ def find_resources_which_failed(hammer_ping_output)
resources_failed
end

private

def map_resources_with_services(resources)
service_names = []
resources.each do |resource|
Expand All @@ -48,8 +163,49 @@ def init_result_obj(success_val = true, message = '', data = [])
end

def exec_hammer_cmd(cmd, required_json = false)
response = ForemanMaintain::Utils::Hammer.instance.run_command(cmd)
response = run(cmd)
json_str = parse_json(response) if required_json
json_str ? json_str : response
end

def load_configuration
config_directories.reverse.each do |path|
full_path = File.expand_path path
next unless File.directory? full_path
load_from_file(File.join(full_path, 'cli_config.yml'))
load_from_file(File.join(full_path, 'defaults.yml'))
# load config for modules
Dir.glob(File.join(full_path, 'cli.modules.d/*.yml')).sort.each do |f|
load_from_file(f)
end
end
end

def load_from_file(file_path)
if File.file? file_path
config = YAML.load(File.open(file_path))
if config
ForemanMaintain::Utils::HashTools.deep_merge!(@configuration, config)
@config_files << file_path
end
end
end

def password_from_answers
return nil unless feature(:installer)
feature(:installer).answers['foreman']['admin_password']
end

def save_config_and_check(config)
save_config(config)
check_connection
end

def save_config(config)
File.open(custom_config_file, 'w', 0o600) { |f| f.puts YAML.dump(config) }
end

def _check_connection
`#{command_base} architecture list 2>&1` && $CHILD_STATUS.exitstatus == 0
end
end
2 changes: 1 addition & 1 deletion definitions/features/sync_plans.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def save_to_storage(storage)
def update_records(ids, enabled)
updated_record_ids = []
ids.each do |sp_id|
result = hammer("sync-plan update --id #{sp_id} --enabled #{enabled}")
result = feature(:hammer).run("sync-plan update --id #{sp_id} --enabled #{enabled}")
if result.include?('Sync plan updated')
updated_record_ids << sp_id
else
Expand Down
43 changes: 4 additions & 39 deletions definitions/procedures/hammer_setup.rb
Original file line number Diff line number Diff line change
@@ -1,50 +1,15 @@
class Procedures::HammerSetup < ForemanMaintain::Procedure
metadata do
description 'Setup hammer'
for_feature :hammer
end

def run
setup_from_default || setup_from_answers
puts "New settings saved into #{hammer.config_file}"
hammer.run_command('architecture list') # if not setup properly, an error will be risen
result = feature(:hammer).setup_admin_access
logger.info 'Hammer was configured successfully.' if result
end

def necessary?
!hammer.ready?
end

private

def setup_from_default
used_default_file = hammer.setup_from_default
if used_default_file
puts "Using defaults from #{used_default_file}"
true
end
end

def setup_from_answers
loop do
username, password = ask_for_credentials
break if username.nil?
if hammer.setup_from_answers(username, password)
return true
else
puts 'Invalid credentials'
end
end
end

def ask_for_credentials
username = ask('Hammer username [admin]:')
return if username.nil?
username = 'admin' if username.empty?
password = ask('Hammer password:', :password => true)
return if password.nil?
[username.strip, password.strip]
end

def hammer
ForemanMaintain::Utils::Hammer.instance
!feature(:hammer).check_connection
end
end
5 changes: 5 additions & 0 deletions lib/foreman_maintain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
module ForemanMaintain
require 'foreman_maintain/core_ext'
require 'foreman_maintain/concerns/logger'
require 'foreman_maintain/concerns/reporter'
require 'foreman_maintain/concerns/finders'
require 'foreman_maintain/concerns/metadata'
require 'foreman_maintain/concerns/scenario_metadata'
Expand Down Expand Up @@ -72,6 +73,10 @@ def cache
ObjectCache.instance
end

def reporter
@reporter ||= ForemanMaintain::Reporter::CLIReporter.new
end

def detector
@detector ||= Detector.new
end
Expand Down
8 changes: 4 additions & 4 deletions lib/foreman_maintain/cli/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ def print_check_info(check)
end

def reporter
@reporter ||= Reporter::CLIReporter.new(STDOUT,
STDIN,
:assumeyes => option_wrapper('assumeyes?'))
@reporter ||= ForemanMaintain.reporter
end

def run_scenario(scenarios)
Expand Down Expand Up @@ -101,7 +99,9 @@ def self.interactive_option
delete_duplicate_assumeyes_if_any

option ['-y', '--assumeyes'], :flag,
'Automatically answer yes for all questions'
'Automatically answer yes for all questions' do |assume|
ForemanMaintain.reporter.assumeyes = assume
end

option(['-w', '--whitelist'], 'whitelist',
'Comma-separated list of labels of steps to be skipped') do |whitelist|
Expand Down
9 changes: 0 additions & 9 deletions lib/foreman_maintain/concerns/hammer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,6 @@ def self.included(base)
preparation_steps { Procedures::HammerSetup.new }
end
end

# Run a hammer command, examples:
# hammer('host list')
def hammer(args)
Utils::Hammer.instance.run_command(args)
end

# TODO: method for specifying that the check that includes this method
# requires hammer to be setup
end
end
end
12 changes: 12 additions & 0 deletions lib/foreman_maintain/concerns/reporter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module ForemanMaintain
module Concerns
module Reporter
extend Forwardable
def_delegators :reporter, :with_spinner, :puts, :print, :ask, :assumeyes?

def reporter
ForemanMaintain.reporter
end
end
end
end
3 changes: 3 additions & 0 deletions lib/foreman_maintain/error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,7 @@ def generate_message
class UsageError < StandardError
end
end

class HammerConfigurationError < StandardError
end
end
Loading

0 comments on commit 8b3e44a

Please sign in to comment.