foreman_maintain 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/bin/foreman-maintain +2 -1
  3. data/definitions/checks/backup/directory_ready.rb +1 -1
  4. data/definitions/checks/restore/validate_backup.rb +77 -0
  5. data/definitions/checks/restore/validate_hostname.rb +20 -0
  6. data/definitions/features/foreman_proxy.rb +6 -1
  7. data/definitions/features/foreman_tasks.rb +5 -1
  8. data/definitions/features/instance.rb +8 -0
  9. data/definitions/features/katello.rb +0 -1
  10. data/definitions/features/mongo.rb +51 -12
  11. data/definitions/features/pulp.rb +2 -1
  12. data/definitions/features/service.rb +36 -0
  13. data/definitions/features/tar.rb +16 -4
  14. data/definitions/procedures/backup/online/pg_global_objects.rb +3 -1
  15. data/definitions/procedures/backup/prepare_directory.rb +1 -1
  16. data/definitions/procedures/backup/pulp.rb +2 -1
  17. data/definitions/procedures/pulp/migrate.rb +21 -0
  18. data/definitions/procedures/restore/candlepin_dump.rb +30 -0
  19. data/definitions/procedures/restore/configs.rb +40 -0
  20. data/definitions/procedures/restore/confirmation.rb +17 -0
  21. data/definitions/procedures/restore/drop_databases.rb +39 -0
  22. data/definitions/procedures/restore/extract_files.rb +70 -0
  23. data/definitions/procedures/restore/foreman_dump.rb +30 -0
  24. data/definitions/procedures/restore/installer_reset.rb +39 -0
  25. data/definitions/procedures/restore/mongo_dump.rb +41 -0
  26. data/definitions/procedures/restore/pg_global_objects.rb +36 -0
  27. data/definitions/procedures/restore/postgres_owner.rb +18 -0
  28. data/definitions/procedures/selinux/set_file_security.rb +22 -0
  29. data/definitions/procedures/service/daemon_reload.rb +18 -0
  30. data/definitions/procedures/service/restart.rb +2 -2
  31. data/definitions/scenarios/restore.rb +91 -0
  32. data/lib/foreman_maintain/cli.rb +3 -1
  33. data/lib/foreman_maintain/cli/restore_command.rb +26 -0
  34. data/lib/foreman_maintain/concerns/base_database.rb +35 -1
  35. data/lib/foreman_maintain/concerns/system_helpers.rb +1 -5
  36. data/lib/foreman_maintain/reporter/cli_reporter.rb +2 -1
  37. data/lib/foreman_maintain/utils/backup.rb +225 -0
  38. data/lib/foreman_maintain/utils/command_runner.rb +4 -2
  39. data/lib/foreman_maintain/utils/mongo_core.rb +49 -0
  40. data/lib/foreman_maintain/version.rb +1 -1
  41. metadata +20 -2
@@ -7,6 +7,7 @@ require 'foreman_maintain/cli/upgrade_command'
7
7
  require 'foreman_maintain/cli/backup_command'
8
8
  require 'foreman_maintain/cli/advanced_command'
9
9
  require 'foreman_maintain/cli/service_command'
10
+ require 'foreman_maintain/cli/restore_command'
10
11
 
11
12
  module ForemanMaintain
12
13
  module Cli
@@ -15,9 +16,10 @@ module ForemanMaintain
15
16
 
16
17
  subcommand 'health', 'Health related commands', HealthCommand
17
18
  subcommand 'upgrade', 'Upgrade related commands', UpgradeCommand
19
+ subcommand 'service', 'Control applicable services', ServiceCommand
18
20
  subcommand 'backup', 'Backup server', BackupCommand
21
+ subcommand 'restore', 'Restore a backup', RestoreCommand
19
22
  subcommand 'advanced', 'Advanced tools for server maintenance', AdvancedCommand
20
- subcommand 'service', 'Control applicable services', ServiceCommand
21
23
 
22
24
  def run(*arguments)
23
25
  logger.info("Running foreman-maintain command with arguments #{arguments.inspect}")
@@ -0,0 +1,26 @@
1
+ module ForemanMaintain
2
+ module Cli
3
+ class RestoreCommand < Base
4
+ interactive_option
5
+ parameter 'BACKUP_DIR', 'Path to backup directory to restore',
6
+ :attribute_name => :backup_dir
7
+
8
+ option ['-i', '--incremental'], :flag, 'Restore an incremental backup',
9
+ :attribute_name => :incremental
10
+
11
+ def execute
12
+ scenario = Scenarios::Restore.new(
13
+ :backup_dir => @backup_dir,
14
+ :incremental_backup => @incremental || incremental_backup?
15
+ )
16
+ run_scenario(scenario)
17
+ exit runner.exit_code
18
+ end
19
+
20
+ def incremental_backup?
21
+ backup = ForemanMaintain::Utils::Backup.new(@backup_dir)
22
+ backup.metadata.fetch(:incremental, false)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -52,13 +52,34 @@ module ForemanMaintain
52
52
  execute!(dump_command(config) + " > #{file}", :hidden_patterns => [config['password']])
53
53
  end
54
54
 
55
+ def restore_dump(file, localdb, config = configuration)
56
+ if localdb
57
+ dump_cmd = "runuser - postgres -c 'pg_restore -C -d postgres #{file}'"
58
+ execute!(dump_cmd)
59
+ else
60
+ # TODO: figure out how to completely ignore errors. Currently this
61
+ # sometimes exits with 1 even though errors are ignored by pg_restore
62
+ dump_cmd = base_command(config, 'pg_restore') +
63
+ ' --no-privileges --clean --disable-triggers -n public ' \
64
+ "-d #{config['database']} #{file}"
65
+ execute!(dump_cmd, :hidden_patterns => [config['password']],
66
+ :valid_exit_statuses => [0, 1])
67
+ end
68
+ end
69
+
70
+ def restore_pg_globals(pg_globals, config = configuration)
71
+ execute!(base_command(config, 'psql') + " -f #{pg_globals} postgres 2>/dev/null",
72
+ :hidden_patterns => [config['password']])
73
+ end
74
+
55
75
  def backup_local(backup_file, extra_tar_options = {})
56
76
  dir = extra_tar_options.fetch(:data_dir, data_dir)
57
77
  FileUtils.cd(dir) do
58
78
  tar_options = {
59
79
  :archive => backup_file,
60
80
  :command => 'create',
61
- :transform => 's,^,var/lib/pgsql/data/,S'
81
+ :transform => 's,^,var/lib/pgsql/data/,S',
82
+ :files => '*'
62
83
  }.merge(extra_tar_options)
63
84
  feature(:tar).run(tar_options)
64
85
  end
@@ -112,6 +133,19 @@ module ForemanMaintain
112
133
  find_dir_containing_file(directory, 'postgresql.conf')
113
134
  end
114
135
 
136
+ def dropdb(config = configuration)
137
+ if local?
138
+ execute!("runuser - postgres -c 'dropdb #{config['database']}'")
139
+ else
140
+ delete_statement = psql(<<-SQL)
141
+ select string_agg('drop table if exists \"' || tablename || '\" cascade;', '')
142
+ from pg_tables
143
+ where schemaname = 'public';
144
+ SQL
145
+ psql(delete_statement)
146
+ end
147
+ end
148
+
115
149
  private
116
150
 
117
151
  def base_command(config, command = 'psql')
@@ -7,6 +7,7 @@ module ForemanMaintain
7
7
  module Concerns
8
8
  module SystemHelpers
9
9
  include Logger
10
+ include Concerns::Finders
10
11
 
11
12
  def self.included(klass)
12
13
  klass.extend(self)
@@ -184,11 +185,6 @@ module ForemanMaintain
184
185
  execute("lvs --noheadings -o lv_path -S lv_name=#{lv_name}").strip
185
186
  end
186
187
 
187
- def local_psql_database?
188
- (feature(:foreman_database) && feature(:foreman_database).local?) ||
189
- (feature(:candlepin_database) && feature(:candlepin_database).local?)
190
- end
191
-
192
188
  def find_dir_containing_file(directory, target)
193
189
  result = nil
194
190
  Find.find(directory) do |path|
@@ -88,7 +88,8 @@ module ForemanMaintain
88
88
  end
89
89
 
90
90
  def before_execution_starts(execution)
91
- logger.info("--- Execution step '#{execution.name}' started ---")
91
+ label = execution.step.label.to_s.tr('_', '-')
92
+ logger.info("--- Execution step '#{execution.name}' [#{label}] started ---")
92
93
  puts(execution_info(execution, ''))
93
94
  end
94
95
 
@@ -0,0 +1,225 @@
1
+ require 'zlib'
2
+ require 'rubygems/package'
3
+ require 'yaml'
4
+
5
+ module ForemanMaintain
6
+ module Utils
7
+ class Backup
8
+ include Concerns::SystemHelpers
9
+
10
+ attr_accessor :standard_files, :katello_online_files, :katello_offline_files,
11
+ :foreman_online_files, :foreman_offline_files, :fpc_offline_files,
12
+ :fpc_online_files
13
+
14
+ def initialize(backup_dir)
15
+ # fpc stands for foreman proxy w/ content
16
+ @backup_dir = backup_dir
17
+ @standard_files = ['config_files.tar.gz']
18
+ @katello_online_files = ['mongo_dump', 'candlepin.dump', 'foreman.dump']
19
+ @katello_offline_files = ['mongo_data.tar.gz', 'pgsql_data.tar.gz']
20
+ @foreman_online_files = ['foreman.dump']
21
+ @foreman_offline_files = ['pgsql_data.tar.gz']
22
+ @fpc_online_files = ['mongo_dump']
23
+ @fpc_offline_files = ['mongo_data.tar.gz']
24
+ end
25
+
26
+ def file_map
27
+ @file_map ||= {
28
+ :mongo_data => map_file(@backup_dir, 'mongo_data.tar.gz'),
29
+ :pgsql_data => map_file(@backup_dir, 'pgsql_data.tar.gz'),
30
+ :pulp_data => map_file(@backup_dir, 'pulp_data.tar'),
31
+ :foreman_dump => map_file(@backup_dir, 'foreman.dump'),
32
+ :candlepin_dump => map_file(@backup_dir, 'candlepin.dump'),
33
+ :mongo_dump => map_file(@backup_dir, 'mongo_dump'),
34
+ :config_files => map_file(@backup_dir, 'config_files.tar.gz'),
35
+ :pg_globals => map_file(@backup_dir, 'pg_globals.dump'),
36
+ :metadata => map_file(@backup_dir, 'metadata.yml')
37
+ }
38
+ end
39
+
40
+ def map_file(backup_dir, filename)
41
+ file_path = File.join(backup_dir, filename)
42
+ present = File.exist?(file_path)
43
+ {
44
+ :present => present,
45
+ :path => file_path
46
+ }
47
+ end
48
+
49
+ def present_files
50
+ present_files = file_map.select { |_k, v| v[:present] }
51
+ present_files.values.map { |f| File.basename(f[:path]) }
52
+ end
53
+
54
+ def valid_backup?
55
+ file_map[:config_files][:present] && check_backup
56
+ end
57
+
58
+ def check_backup
59
+ if feature(:instance).foreman_proxy_with_content?
60
+ valid_fpc_backup?
61
+ elsif feature(:katello)
62
+ valid_katello_backup?
63
+ else
64
+ valid_foreman_backup?
65
+ end
66
+ end
67
+
68
+ def valid_fpc_backup?
69
+ fpc_online_backup? || fpc_standard_backup? || fpc_logical_backup?
70
+ end
71
+
72
+ def valid_katello_backup?
73
+ katello_online_backup? || katello_standard_backup? || katello_logical_backup?
74
+ end
75
+
76
+ def valid_foreman_backup?
77
+ foreman_standard_backup? || foreman_online_backup? || foreman_logical_backup?
78
+ end
79
+
80
+ def katello_standard_backup?
81
+ file_map[:mongo_data][:present] &&
82
+ file_map[:pgsql_data][:present] &&
83
+ !(
84
+ file_map[:candlepin_dump][:present] ||
85
+ file_map[:foreman_dump][:present] ||
86
+ file_map[:mongo_dump][:present]
87
+ )
88
+ end
89
+
90
+ def katello_online_backup?
91
+ file_map[:candlepin_dump][:present] &&
92
+ file_map[:foreman_dump][:present] &&
93
+ file_map[:mongo_dump][:present] &&
94
+ !(
95
+ file_map[:mongo_data][:present] ||
96
+ file_map[:pgsql_data][:present]
97
+ )
98
+ end
99
+
100
+ def katello_logical_backup?
101
+ file_map[:mongo_dump][:present] &&
102
+ file_map[:mongo_data][:present] &&
103
+ file_map[:pgsql_data][:present] &&
104
+ file_map[:candlepin_dump][:present] &&
105
+ file_map[:foreman_dump][:present]
106
+ end
107
+
108
+ def fpc_standard_backup?
109
+ file_map[:mongo_data][:present] &&
110
+ !(
111
+ file_map[:pgsql_data][:present] ||
112
+ file_map[:candlepin_dump][:present] ||
113
+ file_map[:foreman_dump][:present] ||
114
+ file_map[:mongo_dump][:present]
115
+ )
116
+ end
117
+
118
+ def fpc_online_backup?
119
+ file_map[:mongo_dump][:present] &&
120
+ !(
121
+ file_map[:mongo_data][:present] ||
122
+ file_map[:pgsql_data][:present] ||
123
+ file_map[:candlepin_dump][:present] ||
124
+ file_map[:foreman_dump][:present]
125
+ )
126
+ end
127
+
128
+ def fpc_logical_backup?
129
+ file_map[:mongo_dump][:present] &&
130
+ file_map[:mongo_data][:present] &&
131
+ !(
132
+ file_map[:pgsql_data][:present] ||
133
+ file_map[:candlepin_dump][:present] ||
134
+ file_map[:foreman_dump][:present]
135
+ )
136
+ end
137
+
138
+ def foreman_standard_backup?
139
+ file_map[:pgsql_data][:present] &&
140
+ !(
141
+ file_map[:candlepin_dump][:present] ||
142
+ file_map[:foreman_dump][:present] ||
143
+ file_map[:mongo_data][:present] ||
144
+ file_map[:mongo_dump][:present]
145
+ )
146
+ end
147
+
148
+ def foreman_online_backup?
149
+ file_map[:foreman_dump][:present] &&
150
+ !(
151
+ file_map[:candlepin_dump][:present] ||
152
+ file_map[:pgsql_data][:present] ||
153
+ file_map[:mongo_data][:present] ||
154
+ file_map[:mongo_dump][:present]
155
+ )
156
+ end
157
+
158
+ def foreman_logical_backup?
159
+ file_map[:pgsql_data][:present] &&
160
+ file_map[:foreman_dump][:present] &&
161
+ !(
162
+ file_map[:candlepin_dump][:present] ||
163
+ file_map[:mongo_data][:present] ||
164
+ file_map[:mongo_dump][:present]
165
+ )
166
+ end
167
+
168
+ def validate_hostname?
169
+ # make sure that the system hostname is the same as the backup
170
+ config_tarball = file_map[:config_files][:path]
171
+ config_files = tarball_file_list(config_tarball)
172
+
173
+ # Incremental backups sometimes don't include httpd.conf. Since a "base" backup
174
+ # is restored before an incremental, we can assume that the hostname is checked
175
+ # during the base backup restore
176
+ if config_files.include?('etc/httpd/conf/httpd.conf')
177
+ tar_cmd = "tar zxf #{config_tarball} etc/httpd/conf/httpd.conf " \
178
+ "--to-stdout | grep ServerName | awk {'print $2'} | tr -d '\"'"
179
+ backup_hostname = execute(tar_cmd).chomp
180
+ backup_hostname == hostname
181
+ else
182
+ true
183
+ end
184
+ end
185
+
186
+ def metadata
187
+ if file_map[:metadata][:present]
188
+ YAML.load_file(file_map[:metadata][:path])
189
+ else
190
+ {}
191
+ end
192
+ end
193
+
194
+ def pulp_tar_split?
195
+ File.exist?(File.join(@backup_dir, 'pulp_data.part0002'))
196
+ end
197
+
198
+ def tar_backups_exist?
199
+ file_map[:mongo_data][:present] ||
200
+ file_map[:pulp_data][:present] ||
201
+ file_map[:pgsql_data][:present]
202
+ end
203
+
204
+ def sql_dump_files_exist?
205
+ file_map[:foreman_dump][:present] ||
206
+ file_map[:candlepin_dump][:present]
207
+ end
208
+
209
+ private
210
+
211
+ def tarball_file_list(tarball)
212
+ # accepts tar.gz files only
213
+ file_list = []
214
+ File.open(tarball, 'rb') do |file|
215
+ ::Zlib::GzipReader.wrap(file) do |gz|
216
+ ::Gem::Package::TarReader.new(gz) do |tar|
217
+ tar.each { |entry| file_list << entry.full_name }
218
+ end
219
+ end
220
+ end
221
+ file_list
222
+ end
223
+ end
224
+ end
225
+ end
@@ -8,13 +8,15 @@ module ForemanMaintain
8
8
  attr_reader :logger, :command
9
9
 
10
10
  def initialize(logger, command, options)
11
- options.validate_options!(:stdin, :hidden_patterns, :interactive)
11
+ options.validate_options!(:stdin, :hidden_patterns, :interactive, :valid_exit_statuses)
12
+ options[:valid_exit_statuses] ||= [0]
12
13
  @logger = logger
13
14
  @command = command
14
15
  @stdin = options[:stdin]
15
16
  @hidden_patterns = Array(options[:hidden_patterns])
16
17
  @interactive = options[:interactive]
17
18
  @options = options
19
+ @valid_exit_statuses = options[:valid_exit_statuses]
18
20
  raise ArgumentError, 'Can not pass stdin for interactive command' if @interactive && @stdin
19
21
  end
20
22
 
@@ -43,7 +45,7 @@ module ForemanMaintain
43
45
  end
44
46
 
45
47
  def success?
46
- exit_status == 0
48
+ @valid_exit_statuses.include? exit_status
47
49
  end
48
50
 
49
51
  def execution_error
@@ -15,6 +15,10 @@ module ForemanMaintain::Utils
15
15
  def dump_command
16
16
  'mongodump'
17
17
  end
18
+
19
+ def restore_command
20
+ 'mongorestore'
21
+ end
18
22
  end
19
23
 
20
24
  class MongoCore34 < MongoCore
@@ -33,5 +37,50 @@ module ForemanMaintain::Utils
33
37
  def dump_command
34
38
  'scl enable rh-mongodb34 -- mongodump'
35
39
  end
40
+
41
+ def restore_command
42
+ 'scl enable rh-mongodb34 -- mongorestore'
43
+ end
44
+ end
45
+
46
+ class MongoCoreInstalled < MongoCore
47
+ include ForemanMaintain::Concerns::SystemHelpers
48
+
49
+ attr_reader :services, :server_config_files, :client_command, :dump_command
50
+
51
+ def initialize
52
+ @services = {}
53
+ @server_config_files = []
54
+
55
+ detect_mongo_default
56
+ detect_mongo_34
57
+ raise ForemanMaintain::Error::Fail, 'Mongo client was not found' unless @client_command
58
+ end
59
+
60
+ private
61
+
62
+ def detect_mongo_34
63
+ if find_package('rh-mongodb34-mongodb-server')
64
+ @services['rh-mongodb34-mongod'] = 5
65
+ @server_config_files << '/etc/opt/rh/rh-mongodb34/mongod.conf'
66
+ end
67
+
68
+ if find_package('rh-mongodb34-mongodb')
69
+ @client_command = 'scl enable rh-mongodb34 -- mongo'
70
+ @dump_command = 'scl enable rh-mongodb34 -- mongodump'
71
+ end
72
+ end
73
+
74
+ def detect_mongo_default
75
+ if find_package('mongodb-server')
76
+ @services['mongod'] = 5
77
+ @server_config_files << '/etc/mongod.conf'
78
+ end
79
+
80
+ if find_package('mongodb')
81
+ @client_command = 'mongo'
82
+ @dump_command = 'mongodump'
83
+ end
84
+ end
36
85
  end
37
86
  end