chef_backup 0.0.1.dev.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/.kitchen.yml +30 -0
  4. data/.rubocop.yml +17 -0
  5. data/.travis.yml +6 -0
  6. data/Gemfile +4 -0
  7. data/Guardfile +22 -0
  8. data/LICENSE +13 -0
  9. data/README.md +33 -0
  10. data/Rakefile +44 -0
  11. data/chef_backup.gemspec +33 -0
  12. data/lib/chef_backup.rb +12 -0
  13. data/lib/chef_backup/config.rb +57 -0
  14. data/lib/chef_backup/data_map.rb +44 -0
  15. data/lib/chef_backup/exceptions.rb +12 -0
  16. data/lib/chef_backup/helpers.rb +159 -0
  17. data/lib/chef_backup/logger.rb +39 -0
  18. data/lib/chef_backup/runner.rb +136 -0
  19. data/lib/chef_backup/strategy.rb +29 -0
  20. data/lib/chef_backup/strategy/backup/custom.rb +7 -0
  21. data/lib/chef_backup/strategy/backup/ebs.rb +28 -0
  22. data/lib/chef_backup/strategy/backup/lvm.rb +42 -0
  23. data/lib/chef_backup/strategy/backup/object.rb +29 -0
  24. data/lib/chef_backup/strategy/backup/tar.rb +165 -0
  25. data/lib/chef_backup/strategy/restore/custom.rb +0 -0
  26. data/lib/chef_backup/strategy/restore/ebs.rb +0 -0
  27. data/lib/chef_backup/strategy/restore/lvm.rb +0 -0
  28. data/lib/chef_backup/strategy/restore/object.rb +0 -0
  29. data/lib/chef_backup/strategy/restore/tar.rb +125 -0
  30. data/lib/chef_backup/version.rb +4 -0
  31. data/spec/fixtures/chef-server-running.json +584 -0
  32. data/spec/spec_helper.rb +103 -0
  33. data/spec/unit/data_map_spec.rb +59 -0
  34. data/spec/unit/helpers_spec.rb +88 -0
  35. data/spec/unit/runner_spec.rb +185 -0
  36. data/spec/unit/shared_examples/helpers.rb +20 -0
  37. data/spec/unit/strategy/backup/lvm_spec.rb +0 -0
  38. data/spec/unit/strategy/backup/shared_examples/backup.rb +74 -0
  39. data/spec/unit/strategy/backup/tar_spec.rb +280 -0
  40. data/spec/unit/strategy/restore/lvm_spec.rb +0 -0
  41. data/spec/unit/strategy/restore/shared_examples/restore.rb +84 -0
  42. data/spec/unit/strategy/restore/tar_spec.rb +238 -0
  43. data/spec/unit/strategy_spec.rb +36 -0
  44. metadata +253 -0
@@ -0,0 +1,103 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+
4
+ require 'chef_backup'
5
+ require 'bundler/setup'
6
+ require 'json'
7
+ require 'tempfile'
8
+ require 'chef/mixin/deep_merge'
9
+
10
+ # Merge attributes into existing running_config
11
+ def private_chef(*args)
12
+ Chef::Mixin::DeepMerge.deep_merge!(*args, ChefBackup::Config['private_chef'])
13
+ end
14
+
15
+ # Overwrite config with given attributes
16
+ def private_chef!(args = {})
17
+ ChefBackup::Config.config = args
18
+ end
19
+
20
+ # Merge attributes into existing cli_args
21
+ def cli_args(*args)
22
+ Chef::Mixin::DeepMerge.deep_merge!(*args, ChefBackup::Config.config)
23
+ end
24
+
25
+ # Overwrite config with given CLI args
26
+ def cli_args!(args)
27
+ ChefBackup::Config.config = args
28
+ end
29
+
30
+ def use_default_running_config
31
+ ChefBackup::Config.config = running_config
32
+ end
33
+
34
+ def use_default_cli_args
35
+ ChefBackup::Config.config = cli_args
36
+ end
37
+
38
+ def clear_config
39
+ ChefBackup::Config.config = {}
40
+ end
41
+
42
+ def set_common_variables
43
+ let(:backup_tarball) { '/tmp/chef-backup-2014-12-10-20-31-40.tgz' }
44
+ let(:backup_time) { '2014-08-21T23:10:57-07:00' }
45
+ let(:tmp_dir) { '/tmp/chef-backup' }
46
+ let(:strategy) { 'test' }
47
+ let(:export_dir) { '/mnt/chef-backups' }
48
+ let(:all_services) do
49
+ %w(nginx oc_bifrost oc_id opscode-erchef opscode-expander
50
+ opscode-expander-reindexer opscode-solr4 postgresql rabbitmq redis_lb
51
+ )
52
+ end
53
+ let(:enabled_services) { all_services }
54
+ let(:data_map) do
55
+ double(
56
+ 'DataMap',
57
+ services: {
58
+ 'postgresql' => {
59
+ 'data_dir' => '/var/opt/opscode/postgresql_9.2/data'
60
+ },
61
+ 'couchdb' => {
62
+ 'data_dir' => '/var/opt/opscode/couchdb/data'
63
+ },
64
+ 'rabbitmq' => {
65
+ 'data_dir' => '/var/opt/opscode/rabbitdb/data'
66
+ }
67
+ },
68
+ configs: {
69
+ 'opscode' => {
70
+ 'data_dir' => '/etc/opscode'
71
+ },
72
+ 'opscode-manage' => {
73
+ 'data_dir' => '/etc/opscode-manage'
74
+ }
75
+ }
76
+ )
77
+ end
78
+ end
79
+
80
+ def running_config
81
+ @config ||= begin
82
+ f = File.expand_path('../fixtures/chef-server-running.json', __FILE__)
83
+ JSON.parse(File.read(f))
84
+ end
85
+ @config.dup
86
+ end
87
+
88
+ def cli_args
89
+ {
90
+ 'tmp_dir' => '/tmp/chef_backup/tmp_dir',
91
+ 'agree_to_cleanse' => nil,
92
+ 'restore_arg' => '/tmp/chef_backup/backup.tgz',
93
+ 'restore_dir' => File.join('/tmp/chef_backup/tmp_dir', 'restore_dir')
94
+ }
95
+ end
96
+
97
+ RSpec.configure do |rspec|
98
+ rspec.run_all_when_everything_filtered = true
99
+ rspec.filter_run :focus
100
+ rspec.order = 'random'
101
+ rspec.expect_with(:rspec) { |c| c.syntax = :expect }
102
+ rspec.before { allow($stdout).to receive(:write) }
103
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe ChefBackup::DataMap do
4
+ set_common_variables
5
+
6
+ describe '.initialize' do
7
+ it 'yields a config block' do
8
+ expect { |b| described_class.new(&b) }.to yield_with_args
9
+ end
10
+ end
11
+
12
+ describe '.add_service' do
13
+ subject { described_class.new }
14
+
15
+ it 'adds a service' do
16
+ subject.add_service('bookshelf', '/bookshelf/path')
17
+ expect(subject.services.keys.count).to eq(1)
18
+ expect(subject.services['bookshelf']['data_dir']).to eq('/bookshelf/path')
19
+ end
20
+ end
21
+
22
+ describe '.add_config' do
23
+ subject { described_class.new }
24
+
25
+ it 'adds a config' do
26
+ subject.add_config('opscode-manage', '/opscode-manage/path')
27
+ expect(subject.configs.keys.count).to eq(1)
28
+ expect(subject.configs['opscode-manage']['data_dir'])
29
+ .to eq('/opscode-manage/path')
30
+ end
31
+ end
32
+
33
+ describe '.manifest' do
34
+ subject do
35
+ described_class.new do |dm|
36
+ dm.strategy = 'test'
37
+ dm.backup_time = backup_time
38
+ dm.add_config('somethingstrange', 'inthehood')
39
+ dm.add_service('whoyagonnacall', 'ghostbusters')
40
+ end
41
+ end
42
+
43
+ it 'includes the backup strategy type' do
44
+ expect(subject.manifest).to include('strategy' => 'test')
45
+ end
46
+
47
+ it 'includes the backup timestamp' do
48
+ expect(subject.manifest).to include('backup_time' => backup_time)
49
+ end
50
+
51
+ it 'includes the backup config' do
52
+ expect(subject.manifest).to include('configs' => subject.configs)
53
+ end
54
+
55
+ it 'includes the backup service' do
56
+ expect(subject.manifest).to include('services' => subject.services)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+ require_relative 'shared_examples/helpers'
3
+
4
+ describe ChefBackup::Helpers do
5
+ let(:tmp_dir_path) { '/tmp/chef_backup/tmp_dir' }
6
+
7
+ before do
8
+ # Test class to include our helpers methods
9
+ class HelperTest; include ChefBackup::Helpers; end
10
+ end
11
+
12
+ after do
13
+ Object.send(:remove_const, :HelperTest)
14
+ end
15
+
16
+ subject { HelperTest.new }
17
+
18
+ describe '.tmp_dir' do
19
+ context 'with CLI args' do
20
+ before { private_chef!('tmp_dir' => tmp_dir_path) }
21
+
22
+ context 'when the directory exists' do
23
+ before do
24
+ allow(File)
25
+ .to receive(:directory?).with(tmp_dir_path).and_return(true)
26
+ end
27
+
28
+ it_behaves_like '.tmp_dir with an existing specified directory'
29
+ end
30
+
31
+ context 'when the directory does not exist' do
32
+ before do
33
+ allow(File)
34
+ .to receive(:directory?).with(tmp_dir_path).and_return(false)
35
+ allow(FileUtils)
36
+ .to receive(:mkdir_p).with(tmp_dir_path).and_return([tmp_dir_path])
37
+ end
38
+
39
+ it_behaves_like '.tmp_dir with a nonexisting specified directory'
40
+ end
41
+ end
42
+
43
+ context 'with running_config args' do
44
+ before { private_chef!('tmp_dir' => tmp_dir_path) }
45
+
46
+ context 'when the directory exists' do
47
+ before do
48
+ allow(File)
49
+ .to receive(:directory?).with(tmp_dir_path).and_return(true)
50
+ end
51
+
52
+ it_behaves_like '.tmp_dir with an existing specified directory'
53
+ end
54
+
55
+ context 'when the directory does not exist' do
56
+ before do
57
+ allow(File)
58
+ .to receive(:directory?).with(tmp_dir_path).and_return(false)
59
+ allow(FileUtils)
60
+ .to receive(:mkdir_p).with(tmp_dir_path).and_return([tmp_dir_path])
61
+ end
62
+
63
+ it_behaves_like '.tmp_dir with a nonexisting specified directory'
64
+ end
65
+ end
66
+
67
+ context 'when no args are passed' do
68
+ before do
69
+ clear_config
70
+ allow(Dir).to receive(:mktmpdir).with('chef_backup')
71
+ end
72
+
73
+ it_behaves_like '.tmp_dir without a specified directory'
74
+ end
75
+ end
76
+
77
+ describe '.cleanup' do
78
+ before do
79
+ allow(subject).to receive(:tmp_dir).and_return(tmp_dir_path)
80
+ allow(FileUtils).to receive(:rm_r).with(tmp_dir_path)
81
+ end
82
+
83
+ it 'cleans up all items in the temp directory' do
84
+ expect(FileUtils).to receive(:rm_r).with(tmp_dir_path)
85
+ subject.cleanup
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,185 @@
1
+ require 'spec_helper'
2
+
3
+ describe ChefBackup::Runner do
4
+ let(:test_strategy) { double('TestBackup', backup: true, restore: true) }
5
+ let(:backup_tarball) { '/tmp/chef-backup-2014-12-10-20-31-40.tgz' }
6
+ let(:backup_name) { 'chef-backup-2014-12-10-20-31-40' }
7
+ let(:restore_dir) { ChefBackup::Config['restore_dir'] }
8
+ let(:manifest_json) { "#{restore_dir}/manifest.json" }
9
+ let(:manifest) { { 'strategy' => 'test_strategy' } }
10
+ let(:json) { '{"some":{"nested":{"hash":1}}}' }
11
+ let(:runner_config) do
12
+ {
13
+ 'tmp_dir' => '/tmp/runner_spec_tmp_dir',
14
+ 'restore_param' => backup_tarball,
15
+ 'agree_to_cleanse' => 'yes',
16
+ 'private_chef' => { 'backup' => { 'strategy' => 'test' } }
17
+ }
18
+ end
19
+
20
+ subject { described_class.new(runner_config) }
21
+
22
+ describe '.backup' do
23
+ it 'initializes a ChefBackup::Strategy and calls .backup' do
24
+ allow(ChefBackup::Strategy)
25
+ .to receive(:backup)
26
+ .with('test')
27
+ .and_return(test_strategy)
28
+
29
+ expect(test_strategy).to receive(:backup).once
30
+ subject.backup
31
+ end
32
+ end
33
+
34
+ describe '.restore' do
35
+ it 'initializes a ChefBackup::Strategy and calls .restore' do
36
+ allow(subject).to receive(:restore_strategy).and_return('test')
37
+ allow(ChefBackup::Strategy)
38
+ .to receive(:restore)
39
+ .with('test', backup_tarball)
40
+ .and_return(test_strategy)
41
+
42
+ expect(test_strategy).to receive(:restore).once
43
+ subject.restore
44
+ end
45
+ end
46
+
47
+ describe '.manifest' do
48
+ before do
49
+ allow(subject).to receive(:restore_directory).and_return(restore_dir)
50
+ allow(subject).to receive(:ensure_file!).and_return(true)
51
+ allow(File).to receive(:read).with(manifest_json).and_return(json)
52
+ end
53
+
54
+ it 'verifies that the file exists' do
55
+ expect(subject).to receive(:ensure_file!)
56
+ subject.manifest
57
+ end
58
+
59
+ it 'parses the manifest.json' do
60
+ expect(subject.manifest).to eq(JSON.parse(json).to_h)
61
+ end
62
+ end
63
+
64
+ describe '.restore_directory' do
65
+ let(:rest_dir) do
66
+ ChefBackup::Config['tmp_dir'] = '/tmp/chef_backup'
67
+ File.join(ChefBackup::Config['tmp_dir'], backup_name)
68
+ end
69
+
70
+ before(:each) do
71
+ ChefBackup::Config['tmp_dir'] = '/tmp/chef_backup'
72
+ allow(subject).to receive(:backup_name).and_return(backup_name)
73
+ end
74
+
75
+ context 'when the restore directory already exists' do
76
+ before do
77
+ allow(File).to receive(:directory?).and_return(true)
78
+ allow(Dir).to receive(:glob).and_return(%w(a b c))
79
+ allow(FileUtils).to receive(:rm_r).and_return(true)
80
+ end
81
+
82
+ it 'cleans the restore directory' do
83
+ # The directory needs to exist but has not been set in the config
84
+ ChefBackup::Config['restore_dir'] = nil
85
+ expect(FileUtils).to receive(:rm_r).with(%w(a b c))
86
+ subject.restore_directory
87
+ end
88
+
89
+ it 'does not create a new directory' do
90
+ ChefBackup::Config['restore_dir'] = '/tmp/restore_dir'
91
+ subject.restore_directory
92
+ expect(ChefBackup::Config['restore_dir']).to eq(restore_dir)
93
+ end
94
+ end
95
+
96
+ context 'when the restore directory does not exist' do
97
+ before do
98
+ allow(File).to receive(:directory?).and_return(false)
99
+ allow(FileUtils).to receive(:rm_r).and_return(true)
100
+ allow(FileUtils).to receive(:mkdir_p).and_return(true)
101
+ end
102
+
103
+ it 'creates a restore directory in the runner tmp_dir' do
104
+ ChefBackup::Config['restore_dir'] = nil
105
+ expect(FileUtils).to receive(:mkdir_p).with(rest_dir)
106
+ subject.restore_directory
107
+ end
108
+
109
+ it 'updates the config with the restore dir' do
110
+ ChefBackup::Config['restore_dir'] = nil
111
+ subject.restore_directory
112
+ expect(ChefBackup::Config['restore_dir']).to eq(restore_dir)
113
+ end
114
+ end
115
+ end
116
+
117
+ describe '.unpack_tarball' do
118
+ before do
119
+ allow(subject).to receive(:restore_param).and_return(backup_tarball)
120
+ allow(subject).to receive(:restore_directory).and_return(restore_dir)
121
+ allow(subject).to receive(:shell_out!).and_return(true)
122
+ end
123
+
124
+ it 'raises an error if the tarball is invalid' do
125
+ allow(File)
126
+ .to receive(:exist?).with(backup_tarball).and_return(false)
127
+ expect { subject.unpack_tarball }
128
+ .to raise_error(ChefBackup::Exceptions::InvalidTarball,
129
+ "#{backup_tarball} not found")
130
+ end
131
+
132
+ it 'explodes the tarball into the restore directory' do
133
+ allow(subject).to receive(:ensure_file!).and_return(true)
134
+
135
+ cmd = "tar zxf #{backup_tarball} -C #{restore_dir}"
136
+ expect(subject).to receive(:shell_out!).with(cmd)
137
+ subject.unpack_tarball
138
+ end
139
+ end
140
+
141
+ describe '.restore_strategy' do
142
+ context 'when the restore param is a tarball' do
143
+ before do
144
+ allow(subject).to receive(:tarball?).and_return(true)
145
+ allow(subject).to receive(:unpack_tarball).and_return(true)
146
+ allow(subject).to receive(:manifest).and_return(manifest)
147
+ end
148
+
149
+ it 'unpacks the tarball' do
150
+ expect(subject).to receive(:unpack_tarball)
151
+ subject.restore_strategy
152
+ end
153
+
154
+ it 'returns the strategy from the manifest' do
155
+ expect(subject.restore_strategy).to eq('test_strategy')
156
+ subject.restore_strategy
157
+ end
158
+ end
159
+
160
+ context 'when the restore param is an ebs snapshot' do
161
+ before do
162
+ allow(subject).to receive(:tarball?).and_return(false)
163
+ allow(subject).to receive(:ebs_snapshot?).and_return(true)
164
+ end
165
+
166
+ it 'returns "ebs" as the strategy' do
167
+ expect(subject.restore_strategy).to eq('ebs')
168
+ end
169
+ end
170
+
171
+ context 'when the restore param is not valid' do
172
+ before do
173
+ allow(subject).to receive(:tarball?).and_return(false)
174
+ allow(subject).to receive(:ebs_snapshot?).and_return(false)
175
+ allow(subject).to receive(:restore_param).and_return('invalid_param')
176
+ end
177
+
178
+ it 'raises an exception' do
179
+ expect { subject.restore_strategy }
180
+ .to raise_error(ChefBackup::Exceptions::InvalidStrategy,
181
+ 'invalid_param is not a valid backup')
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,20 @@
1
+ shared_examples '.tmp_dir with a nonexisting specified directory' do
2
+ it 'uses the specified directory' do
3
+ expect(FileUtils).to receive(:mkdir_p).with(tmp_dir_path)
4
+ expect(subject.tmp_dir).to eq(tmp_dir_path)
5
+ end
6
+ end
7
+
8
+ shared_examples '.tmp_dir with an existing specified directory' do
9
+ it 'does not create a directory' do
10
+ expect(FileUtils).to_not receive(:anything)
11
+ expect(subject.tmp_dir).to eq(tmp_dir_path)
12
+ end
13
+ end
14
+
15
+ shared_examples '.tmp_dir without a specified directory' do
16
+ it 'creates a temp directory' do
17
+ expect(Dir).to receive(:mktmpdir).with('chef_backup')
18
+ subject.tmp_dir
19
+ end
20
+ end