chef_backup 0.0.1.dev.4 → 0.2.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +5 -5
  2. data/lib/chef_backup/config.rb +22 -12
  3. data/lib/chef_backup/data_map.rb +18 -12
  4. data/lib/chef_backup/deep_merge.rb +145 -0
  5. data/lib/chef_backup/helpers.rb +154 -28
  6. data/lib/chef_backup/logger.rb +11 -6
  7. data/lib/chef_backup/mash.rb +226 -0
  8. data/lib/chef_backup/runner.rb +24 -21
  9. data/lib/chef_backup/strategy/backup/custom.rb +1 -2
  10. data/lib/chef_backup/strategy/backup/ebs.rb +3 -6
  11. data/lib/chef_backup/strategy/backup/lvm.rb +2 -4
  12. data/lib/chef_backup/strategy/backup/object.rb +2 -4
  13. data/lib/chef_backup/strategy/backup/tar.rb +96 -40
  14. data/lib/chef_backup/strategy/restore/tar.rb +81 -51
  15. data/lib/chef_backup/strategy.rb +10 -10
  16. data/lib/chef_backup/version.rb +1 -1
  17. data/lib/chef_backup.rb +8 -8
  18. metadata +21 -162
  19. data/.gitignore +0 -23
  20. data/.kitchen.yml +0 -30
  21. data/.rubocop.yml +0 -21
  22. data/.travis.yml +0 -6
  23. data/Gemfile +0 -4
  24. data/Guardfile +0 -22
  25. data/README.md +0 -21
  26. data/Rakefile +0 -44
  27. data/chef_backup.gemspec +0 -33
  28. data/spec/fixtures/chef-server-running.json +0 -589
  29. data/spec/spec_helper.rb +0 -103
  30. data/spec/unit/data_map_spec.rb +0 -59
  31. data/spec/unit/helpers_spec.rb +0 -88
  32. data/spec/unit/runner_spec.rb +0 -185
  33. data/spec/unit/shared_examples/helpers.rb +0 -20
  34. data/spec/unit/strategy/backup/lvm_spec.rb +0 -0
  35. data/spec/unit/strategy/backup/shared_examples/backup.rb +0 -92
  36. data/spec/unit/strategy/backup/tar_spec.rb +0 -294
  37. data/spec/unit/strategy/restore/lvm_spec.rb +0 -0
  38. data/spec/unit/strategy/restore/shared_examples/restore.rb +0 -84
  39. data/spec/unit/strategy/restore/tar_spec.rb +0 -238
  40. data/spec/unit/strategy_spec.rb +0 -36
@@ -0,0 +1,226 @@
1
+ # Copyright 2009-2016, Dan Kubb
2
+
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ # ---
23
+ # ---
24
+
25
+ # Some portions of blank.rb and mash.rb are verbatim copies of software
26
+ # licensed under the MIT license. That license is included below:
27
+
28
+ # Copyright 2005-2016, David Heinemeier Hansson
29
+
30
+ # Permission is hereby granted, free of charge, to any person obtaining
31
+ # a copy of this software and associated documentation files (the
32
+ # "Software"), to deal in the Software without restriction, including
33
+ # without limitation the rights to use, copy, modify, merge, publish,
34
+ # distribute, sublicense, and/or sell copies of the Software, and to
35
+ # permit persons to whom the Software is furnished to do so, subject to
36
+ # the following conditions:
37
+
38
+ # The above copyright notice and this permission notice shall be
39
+ # included in all copies or substantial portions of the Software.
40
+
41
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
42
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
43
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
44
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
45
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
46
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
47
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
48
+
49
+ # This class has dubious semantics and we only have it so that people can write
50
+ # params[:key] instead of params['key'].
51
+ class Mash < Hash
52
+
53
+ # @param constructor<Object>
54
+ # The default value for the mash. Defaults to an empty hash.
55
+ #
56
+ # @details [Alternatives]
57
+ # If constructor is a Hash, a new mash will be created based on the keys of
58
+ # the hash and no default value will be set.
59
+ def initialize(constructor = {})
60
+ if constructor.is_a?(Hash)
61
+ super()
62
+ update(constructor)
63
+ else
64
+ super(constructor)
65
+ end
66
+ end
67
+
68
+ # @param orig<Object> Mash being copied
69
+ #
70
+ # @return [Object] A new copied Mash
71
+ def initialize_copy(orig)
72
+ super
73
+ # Handle nested values
74
+ each do |k, v|
75
+ if v.kind_of?(Mash) || v.is_a?(Array)
76
+ self[k] = v.dup
77
+ end
78
+ end
79
+ self
80
+ end
81
+
82
+ # @param key<Object> The default value for the mash. Defaults to nil.
83
+ #
84
+ # @details [Alternatives]
85
+ # If key is a Symbol and it is a key in the mash, then the default value will
86
+ # be set to the value matching the key.
87
+ def default(key = nil)
88
+ if key.is_a?(Symbol) && include?(key = key.to_s)
89
+ self[key]
90
+ else
91
+ super
92
+ end
93
+ end
94
+
95
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
96
+ alias_method :regular_update, :update unless method_defined?(:regular_update)
97
+
98
+ # @param key<Object> The key to set.
99
+ # @param value<Object>
100
+ # The value to set the key to.
101
+ #
102
+ # @see Mash#convert_key
103
+ # @see Mash#convert_value
104
+ def []=(key, value)
105
+ regular_writer(convert_key(key), convert_value(value))
106
+ end
107
+
108
+ # @param other_hash<Hash>
109
+ # A hash to update values in the mash with. The keys and the values will be
110
+ # converted to Mash format.
111
+ #
112
+ # @return [Mash] The updated mash.
113
+ def update(other_hash)
114
+ other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
115
+ self
116
+ end
117
+
118
+ alias_method :merge!, :update
119
+
120
+ # @param key<Object> The key to check for. This will be run through convert_key.
121
+ #
122
+ # @return [Boolean] True if the key exists in the mash.
123
+ def key?(key)
124
+ super(convert_key(key))
125
+ end
126
+
127
+ # def include? def has_key? def member?
128
+ alias_method :include?, :key?
129
+ alias_method :has_key?, :key?
130
+ alias_method :member?, :key?
131
+
132
+ # @param key<Object> The key to fetch. This will be run through convert_key.
133
+ # @param *extras<Array> Default value.
134
+ #
135
+ # @return [Object] The value at key or the default value.
136
+ def fetch(key, *extras)
137
+ super(convert_key(key), *extras)
138
+ end
139
+
140
+ # @param *indices<Array>
141
+ # The keys to retrieve values for. These will be run through +convert_key+.
142
+ #
143
+ # @return [Array] The values at each of the provided keys
144
+ def values_at(*indices)
145
+ indices.collect { |key| self[convert_key(key)] }
146
+ end
147
+
148
+ # @param hash<Hash> The hash to merge with the mash.
149
+ #
150
+ # @return [Mash] A new mash with the hash values merged in.
151
+ def merge(hash)
152
+ self.dup.update(hash)
153
+ end
154
+
155
+ # @param key<Object>
156
+ # The key to delete from the mash.\
157
+ def delete(key)
158
+ super(convert_key(key))
159
+ end
160
+
161
+ # @param *rejected<Array[(String, Symbol)] The mash keys to exclude.
162
+ #
163
+ # @return [Mash] A new mash without the selected keys.
164
+ #
165
+ # @example
166
+ # { :one => 1, :two => 2, :three => 3 }.except(:one)
167
+ # #=> { "two" => 2, "three" => 3 }
168
+ def except(*keys)
169
+ super(*keys.map { |k| convert_key(k) })
170
+ end
171
+
172
+ # Used to provide the same interface as Hash.
173
+ #
174
+ # @return [Mash] This mash unchanged.
175
+ def stringify_keys!; self end
176
+
177
+ # @return [Hash] The mash as a Hash with symbolized keys.
178
+ def symbolize_keys
179
+ h = Hash.new(default)
180
+ each { |key, val| h[key.to_sym] = val }
181
+ h
182
+ end
183
+
184
+ # @return [Hash] The mash as a Hash with string keys.
185
+ def to_hash
186
+ Hash.new(default).merge(self)
187
+ end
188
+
189
+ # @return [Mash] Convert a Hash into a Mash
190
+ # The input Hash's default value is maintained
191
+ def self.from_hash(hash)
192
+ mash = Mash.new(hash)
193
+ mash.default = hash.default
194
+ mash
195
+ end
196
+
197
+ protected
198
+
199
+ # @param key<Object> The key to convert.
200
+ #
201
+ # @param [Object]
202
+ # The converted key. If the key was a symbol, it will be converted to a
203
+ # string.
204
+ #
205
+ # @api private
206
+ def convert_key(key)
207
+ key.kind_of?(Symbol) ? key.to_s : key
208
+ end
209
+
210
+ # @param value<Object> The value to convert.
211
+ #
212
+ # @return [Object]
213
+ # The converted value. A Hash or an Array of hashes, will be converted to
214
+ # their Mash equivalents.
215
+ #
216
+ # @api private
217
+ def convert_value(value)
218
+ if value.class == Hash
219
+ Mash.from_hash(value)
220
+ elsif value.is_a?(Array)
221
+ value.collect { |e| convert_value(e) }
222
+ else
223
+ value
224
+ end
225
+ end
226
+ end
@@ -1,5 +1,5 @@
1
- require 'fileutils'
2
- require 'pathname'
1
+ require "fileutils" unless defined?(FileUtils)
2
+ require "pathname" unless defined?(Pathname)
3
3
 
4
4
  module ChefBackup
5
5
  # ChefBackup::Runner class initializes the strategy and runs the action
@@ -7,8 +7,6 @@ module ChefBackup
7
7
  include ChefBackup::Helpers
8
8
  include ChefBackup::Exceptions
9
9
 
10
- attr_reader :restore_param
11
-
12
10
  #
13
11
  # @param running_config [Hash] A hash of the private-chef-running.json
14
12
  # or the CLI args for a restore
@@ -17,7 +15,7 @@ module ChefBackup
17
15
  #
18
16
  def initialize(running_config)
19
17
  ChefBackup::Config.config = running_config
20
- ChefBackup::Logger.logger(private_chef['backup']['logfile'] || nil)
18
+ ChefBackup::Logger.logger(service_config["backup"]["logfile"] || nil)
21
19
  end
22
20
 
23
21
  #
@@ -28,6 +26,13 @@ module ChefBackup
28
26
  @backup.backup
29
27
  end
30
28
 
29
+ #
30
+ # @return [ChefBackup::Config]
31
+ #
32
+ def config
33
+ ChefBackup::Config.config
34
+ end
35
+
31
36
  #
32
37
  # @return [TrueClass, FalseClass] Execute Chef Server restore
33
38
  #
@@ -40,7 +45,7 @@ module ChefBackup
40
45
  # @return [String] String name of the configured backup strategy
41
46
  #
42
47
  def backup_strategy
43
- config['private_chef']['backup']['strategy']
48
+ service_config["backup"]["strategy"]
44
49
  end
45
50
 
46
51
  #
@@ -48,23 +53,21 @@ module ChefBackup
48
53
  # or a path to a tarball
49
54
  #
50
55
  def restore_param
51
- config['restore_param']
56
+ config["restore_param"]
52
57
  end
53
58
 
54
59
  #
55
60
  # @return [String] A path to backup tarball or EBS snapshot ID
56
61
  #
57
62
  def restore_strategy
58
- @restore_strategy ||= begin
59
- if tarball?
60
- unpack_tarball
61
- manifest['strategy']
62
- elsif ebs_snapshot?
63
- 'ebs'
64
- else
65
- fail InvalidStrategy, "#{restore_param} is not a valid backup"
66
- end
67
- end
63
+ @restore_strategy ||= if tarball?
64
+ unpack_tarball
65
+ manifest["strategy"]
66
+ elsif ebs_snapshot?
67
+ "ebs"
68
+ else
69
+ raise InvalidStrategy, "#{restore_param} is not a valid backup"
70
+ end
68
71
  end
69
72
 
70
73
  #
@@ -72,7 +75,7 @@ module ChefBackup
72
75
  #
73
76
  def tarball?
74
77
  file = Pathname.new(File.expand_path(restore_param))
75
- file.exist? && file.extname == '.tgz'
78
+ file.exist? && file.extname == ".tgz"
76
79
  end
77
80
 
78
81
  #
@@ -97,7 +100,7 @@ module ChefBackup
97
100
  #
98
101
  def backup_name
99
102
  if tarball?
100
- Pathname.new(restore_param).basename.sub_ext('').to_s
103
+ Pathname.new(restore_param).basename.sub_ext("").to_s
101
104
  elsif ebs_snapshot?
102
105
  restore_param
103
106
  end
@@ -110,7 +113,7 @@ module ChefBackup
110
113
  # @return [String] A path to the restore directory
111
114
  #
112
115
  def restore_directory
113
- config['restore_dir'] ||= begin
116
+ config["restore_dir"] ||= begin
114
117
  dir_name = File.join(tmp_dir, backup_name)
115
118
  if File.directory?(dir_name)
116
119
  # clean restore directory if it exists
@@ -128,7 +131,7 @@ module ChefBackup
128
131
  def manifest
129
132
  @manifest ||= begin
130
133
  file = "#{restore_directory}/manifest.json"
131
- ensure_file!(file, InvalidTarball, 'No manifest found in tarball')
134
+ ensure_file!(file, InvalidTarball, "No manifest found in tarball")
132
135
  JSON.parse(File.read(file))
133
136
  end
134
137
  end
@@ -2,6 +2,5 @@
2
2
  # - Verify that backup executable exists and is runnable
3
3
  # - exec script
4
4
  class ChefBackup::Strategy::CustomBackup < ChefBackup::Strategy::TarBackup
5
- def backup
6
- end
5
+ def backup; end
7
6
  end
@@ -17,12 +17,9 @@ class ChefBackup::Strategy::EbsBackup < ChefBackup::Strategy::TarBackup
17
17
  cleanup
18
18
  end
19
19
 
20
- def verify_ebs
21
- end
20
+ def verify_ebs; end
22
21
 
23
- def take_ebs_snapshot
24
- end
22
+ def take_ebs_snapshot; end
25
23
 
26
- def copy_opscode_config
27
- end
24
+ def copy_opscode_config; end
28
25
  end
@@ -34,9 +34,7 @@ class ChefBackup::Strategy::LvmBackup < ChefBackup::Strategy::TarBackup
34
34
  # warn if vg space is low
35
35
  end
36
36
 
37
- def take_lvm_snapshot
38
- end
37
+ def take_lvm_snapshot; end
39
38
 
40
- def mount_lvm_snapshot
41
- end
39
+ def mount_lvm_snapshot; end
42
40
  end
@@ -21,9 +21,7 @@ class ChefBackup::Strategy::ObjectBackup < ChefBackup::Strategy::TarBackup
21
21
  cleanup
22
22
  end
23
23
 
24
- def verify_object
25
- end
24
+ def verify_object; end
26
25
 
27
- def knife_ec_backup
28
- end
26
+ def knife_ec_backup; end
29
27
  end
@@ -1,7 +1,6 @@
1
1
  require 'fileutils'
2
2
  require 'json'
3
3
  require 'time'
4
- require 'highline/import'
5
4
 
6
5
  # rubocop:disable IndentationWidth
7
6
  module ChefBackup
@@ -27,8 +26,8 @@ class TarBackup
27
26
  def export_dir
28
27
  @export_dir ||= begin
29
28
  dir =
30
- if private_chef['backup']['export_dir']
31
- private_chef['backup']['export_dir']
29
+ if service_config['backup']['export_dir']
30
+ service_config['backup']['export_dir']
32
31
  else
33
32
  msg = ["backup['export_dir'] has not been set.",
34
33
  'defaulting to: /var/opt/chef-backups'].join(' ')
@@ -47,39 +46,65 @@ class TarBackup
47
46
  #
48
47
  def dump_db
49
48
  return true unless pg_dump?
50
- pg_user = private_chef['postgresql']['username']
49
+ if external_pg?
50
+ log('Cannot backup external postgresql', :warn)
51
+ return false
52
+ end
53
+ pg_user = service_config['postgresql']['username']
51
54
  sql_file = "#{tmp_dir}/chef_backup-#{backup_time}.sql"
52
- cmd = ['/opt/opscode/embedded/bin/chpst',
55
+ cmd = [chpst,
53
56
  "-u #{pg_user}",
54
- '/opt/opscode/embedded/bin/pg_dumpall',
55
- "> #{sql_file}"
56
- ].join(' ')
57
+ pg_dumpall,
58
+ "> #{sql_file}"].join(' ')
57
59
  log "Dumping Postgresql database to #{sql_file}"
58
- shell_out!(cmd)
60
+ shell_out!(cmd, env: ["PGOPTIONS=#{pg_options}"])
59
61
  data_map.services['postgresql']['pg_dump_success'] = true
60
62
  data_map.services['postgresql']['username'] = pg_user
61
63
  true
62
64
  end
63
65
 
66
+ def chpst
67
+ "#{base_install_dir}/embedded/bin/chpst"
68
+ end
69
+
70
+ def pg_dumpall
71
+ "#{base_install_dir}/embedded/bin/pg_dumpall"
72
+ end
73
+
64
74
  def populate_data_map
65
- stateful_services.each do |service|
66
- next unless private_chef.key?(service)
67
- data_map.add_service(service, private_chef[service]['data_dir'])
75
+ unless config_only?
76
+ stateful_services.each do |service|
77
+ next unless service_config.key?(service)
78
+ data_map.add_service(service, service_config[service]['data_dir'])
79
+ end
68
80
  end
69
81
 
70
82
  config_directories.each do |config|
71
83
  data_map.add_config(config, "/etc/#{config}")
72
84
  end
73
85
 
86
+ populate_versions
87
+
74
88
  # Don't forget the upgrades!
75
- if private_chef.key?('upgrades')
76
- data_map.add_service('upgrades', private_chef['upgrades']['dir'])
89
+ if service_config.key?('upgrades')
90
+ data_map.add_service('upgrades', service_config['upgrades']['dir'])
91
+ end
92
+
93
+ add_ha_services
94
+ end
95
+
96
+ def populate_versions
97
+ project_names.each do |project|
98
+ path = File.join(addon_install_dir(project), '/version-manifest.json')
99
+ data_map.add_version(project, version_from_manifest_file(path))
77
100
  end
101
+ end
78
102
 
79
- if ha?
80
- data_map.add_service('keepalived', private_chef['keepalived']['dir'])
81
- data_map.add_ha_info('provider', private_chef['ha']['provider'])
82
- data_map.add_ha_info('path', private_chef['ha']['path'])
103
+ def add_ha_services
104
+ if ha? && !config_only?
105
+ data_map.add_service('keepalived', service_config['keepalived']['dir'])
106
+ data_map.add_ha_info('provider', service_config['ha']['provider'])
107
+ data_map.add_ha_info('path', service_config['ha']['path'])
83
108
  end
84
109
  end
85
110
 
@@ -94,22 +119,29 @@ class TarBackup
94
119
  end
95
120
  end
96
121
 
122
+ DEFAULT_STATEFUL_SERVICES = %w[rabbitmq
123
+ opscode-solr4
124
+ elasticsearch
125
+ redis_lb
126
+ postgresql
127
+ bookshelf].freeze
128
+
97
129
  def stateful_services
98
- if private_chef.key?('drbd') && private_chef['drbd']['enable'] == true
130
+ if service_config.key?('drbd') && service_config['drbd']['enable'] == true
99
131
  ['drbd']
100
132
  else
101
- %w(
102
- rabbitmq
103
- opscode-solr4
104
- redis_lb
105
- postgresql
106
- bookshelf
107
- )
133
+ DEFAULT_STATEFUL_SERVICES.select do |service|
134
+ service_enabled?(service)
135
+ end
108
136
  end
109
137
  end
110
138
 
111
139
  def config_directories
112
- %w(opscode) + enabled_addons
140
+ [project_name] + enabled_addons.keys
141
+ end
142
+
143
+ def project_names
144
+ ([project_name] + enabled_addons.keys).uniq
113
145
  end
114
146
 
115
147
  # The data_map is a working record of all of the data that is backed up.
@@ -123,30 +155,32 @@ class TarBackup
123
155
 
124
156
  def not_implemented
125
157
  msg = "#{caller[0].split[1]} is not implemented for this strategy"
126
- fail NotImplementedError, msg
158
+ raise NotImplementedError, msg
127
159
  end
128
160
 
129
161
  def backup
130
- log 'Starting Chef Server backup'
162
+ log "Starting Chef Server backup #{config_only? ? '(config only)' : ''}"
131
163
  populate_data_map
132
- if backend?
164
+ stopped = false
165
+ if backend? && !config_only?
133
166
  if !online?
134
167
  ask_to_go_offline unless offline_permission_granted?
135
- stop_chef_server(except: [:keepalived, :postgresql])
168
+ stop_chef_server(except: %i[keepalived postgresql])
136
169
  dump_db
137
170
  stop_service(:postgresql)
171
+ stopped = true
138
172
  else
139
173
  dump_db
140
174
  end
141
175
  end
142
176
  write_manifest
143
177
  create_tarball
144
- start_chef_server if backend? && !online?
178
+ start_chef_server if stopped
145
179
  export_tarball
146
180
  cleanup
147
181
  log 'Backup Complete!'
148
182
  rescue => e
149
- log "Something wen't terribly wrong, aborting backup", :error
183
+ log "Something went terribly wrong, aborting backup", :error
150
184
  log e.message, :error
151
185
  cleanup
152
186
  start_chef_server
@@ -156,31 +190,52 @@ class TarBackup
156
190
  def create_tarball
157
191
  log 'Creating backup tarball'
158
192
  cmd = [
159
- "tar -czf #{tmp_dir}/chef-backup-#{backup_time}.tgz",
193
+ "tar -czf #{tmp_dir}/#{export_filename}",
160
194
  data_map.services.map { |_, v| v['data_dir'] }.compact.join(' '),
161
195
  data_map.configs.map { |_, v| v['data_dir'] }.compact.join(' '),
162
196
  Dir["#{tmp_dir}/*"].map { |f| File.basename(f) }.join(' ')
163
197
  ].join(' ').strip
164
198
 
165
- res = shell_out(cmd, cwd: tmp_dir)
199
+ res = shell_out!(cmd, cwd: tmp_dir)
166
200
  res
167
201
  end
168
202
 
169
203
  def export_tarball
170
204
  log "Exporting tarball to #{export_dir}"
171
- cmd = "rsync -chaz #{tmp_dir}/chef-backup-#{backup_time}.tgz #{export_dir}/"
205
+ cmd = "rsync -chaz #{tmp_dir}/#{export_filename} #{export_dir}/"
172
206
 
173
- res = shell_out(cmd)
207
+ res = shell_out!(cmd)
174
208
  res
175
209
  end
176
210
 
211
+ def export_filename
212
+ postfix = if config_only?
213
+ '-config'
214
+ else
215
+ ''
216
+ end
217
+ "chef-backup#{postfix}-#{backup_time}.tgz"
218
+ end
219
+
220
+ def service_enabled?(service)
221
+ service_config[service] && service_config[service]['enable'] && !service_config[service]['external']
222
+ end
223
+
224
+ def external_pg?
225
+ service_config['postgresql']['external']
226
+ end
227
+
177
228
  def pg_dump?
178
229
  # defaults to true
179
- private_chef['backup']['always_dump_db']
230
+ service_config['backup']['always_dump_db']
180
231
  end
181
232
 
182
233
  def offline_permission_granted?
183
- private_chef['backup']['agree_to_go_offline']
234
+ service_config['backup']['agree_to_go_offline']
235
+ end
236
+
237
+ def config_only?
238
+ service_config['backup']['config_only']
184
239
  end
185
240
 
186
241
  def ask_to_go_offline
@@ -188,7 +243,8 @@ class TarBackup
188
243
  msg << 'continuing. You can skip this message by passing a "--yes" '
189
244
  msg << 'argument. Do you wish to proceed? (y/N):'
190
245
 
191
- exit(1) unless ask(msg) =~ /^y/i
246
+ require "tty-prompt" unless defined?(TTY::Prompt)
247
+ exit(1) unless TTY::Prompt.new(interrupt: :exit).ask(msg) =~ /^y/i
192
248
  end
193
249
  end
194
250
  end