easy_backup 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/README.md +35 -0
- data/Rakefile +2 -0
- data/easy_backup.gemspec +31 -0
- data/lib/easy_backup/adapter/db/postgre_sql.rb +81 -0
- data/lib/easy_backup/adapter/file_system.rb +83 -0
- data/lib/easy_backup/adapter/sftp.rb +36 -0
- data/lib/easy_backup/base.rb +46 -0
- data/lib/easy_backup/configuration.rb +27 -0
- data/lib/easy_backup/extension/net_sftp_session.rb +13 -0
- data/lib/easy_backup/frequency.rb +13 -0
- data/lib/easy_backup/runner.rb +9 -0
- data/lib/easy_backup/version.rb +3 -0
- data/lib/easy_backup.rb +59 -0
- data/spec/config.yaml +11 -0
- data/spec/execution/runner_spec.rb +131 -0
- data/spec/execution/scheduled_spec.rb +29 -0
- data/spec/files/config/backup_config.rb +20 -0
- data/spec/files/data/sample.json +4 -0
- data/spec/files/data/txt/1/text1.txt +1 -0
- data/spec/files/data/txt/2/text2.txt +1 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/specification/configuration_spec.rb +100 -0
- data/spec/specification/easy_backup_spec.rb +34 -0
- data/spec/specification/file_system_adapter_spec.rb +39 -0
- data/spec/specification/postgre_sql_adapter_spec.rb +36 -0
- data/spec/specification/sftp_adapter_spec.rb +18 -0
- data/spec/support/helpers/config_helper.rb +7 -0
- data/spec/support/helpers/postgre_sql_helper.rb +25 -0
- metadata +175 -0
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
## DSL Example
|
2
|
+
|
3
|
+
``` rb
|
4
|
+
EasyBackup.config do
|
5
|
+
|
6
|
+
save PostgreSQL do
|
7
|
+
host 'localhost'
|
8
|
+
port 5432
|
9
|
+
database 'db_production'
|
10
|
+
username 'user_prod'
|
11
|
+
password 'password'
|
12
|
+
zip
|
13
|
+
end
|
14
|
+
|
15
|
+
save FileSystem do
|
16
|
+
folder 'c:/resources'
|
17
|
+
file 'c:/data/file.txt'
|
18
|
+
zip 'data.zip'
|
19
|
+
end
|
20
|
+
|
21
|
+
into FileSystem do
|
22
|
+
folder lambda { "c:/backups/#{Time.now.strftime('%Y%m%d%H%M%S')}" }
|
23
|
+
end
|
24
|
+
|
25
|
+
into SFTP do
|
26
|
+
host 'remote'
|
27
|
+
username 'user_sftp'
|
28
|
+
password 'password'
|
29
|
+
folder "backups/#{Time.now.strftime('%Y%m%d%H%M%S')}"
|
30
|
+
end
|
31
|
+
|
32
|
+
every 1.day, from: 'today at 22:30'
|
33
|
+
|
34
|
+
end
|
35
|
+
```
|
data/Rakefile
ADDED
data/easy_backup.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "easy_backup/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "easy_backup"
|
7
|
+
s.version = EasyBackup::VERSION
|
8
|
+
s.authors = ["Gabriel Naiman"]
|
9
|
+
s.email = ["gabynaiman@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/gabynaiman/easy_backup"
|
11
|
+
s.summary = "Easy DSL to program backups"
|
12
|
+
s.description = "Easy DSL to program backups"
|
13
|
+
|
14
|
+
s.rubyforge_project = "easy_backup"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency 'rubyzip'
|
22
|
+
s.add_dependency 'net-sftp'
|
23
|
+
s.add_dependency 'active_support'
|
24
|
+
s.add_dependency 'i18n'
|
25
|
+
s.add_dependency 'chronic'
|
26
|
+
s.add_dependency 'rufus-scheduler'
|
27
|
+
|
28
|
+
s.add_development_dependency 'sequel'
|
29
|
+
s.add_development_dependency 'pg'
|
30
|
+
s.add_development_dependency "rspec"
|
31
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'zip/zip'
|
3
|
+
|
4
|
+
include Zip
|
5
|
+
|
6
|
+
module EasyBackup
|
7
|
+
module Adapter
|
8
|
+
module Db
|
9
|
+
class PostgreSQL
|
10
|
+
|
11
|
+
def host(host=nil)
|
12
|
+
host ? @host = host : (@host || 'localhost')
|
13
|
+
end
|
14
|
+
|
15
|
+
def database(database=nil)
|
16
|
+
database ? @database = database : @database
|
17
|
+
end
|
18
|
+
|
19
|
+
def username(username=nil)
|
20
|
+
username ? @username = username : (@username || 'postgres')
|
21
|
+
end
|
22
|
+
|
23
|
+
def password(password=nil)
|
24
|
+
password ? @password = password : @password
|
25
|
+
end
|
26
|
+
|
27
|
+
def port(port=nil)
|
28
|
+
port ? @port = port : (@port || 5432)
|
29
|
+
end
|
30
|
+
|
31
|
+
def dump_file(file_name=nil)
|
32
|
+
if file_name
|
33
|
+
@dump_file = file_name
|
34
|
+
else
|
35
|
+
if @dump_file
|
36
|
+
@dump_file.is_a?(Proc) ? @dump_file.call : @dump_file
|
37
|
+
else
|
38
|
+
"#{database}_#{Time.now.strftime('%Y%m%d%H%M%S')}.sql"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def zip_file(file_name=nil)
|
44
|
+
file_name ? @zip_file = file_name : @zip_file
|
45
|
+
end
|
46
|
+
|
47
|
+
def zip
|
48
|
+
zip_file "#{File.basename(dump_file, '.*')}.zip"
|
49
|
+
end
|
50
|
+
|
51
|
+
def send_to(storages)
|
52
|
+
FileUtils.mkpath File.dirname(path_to(dump_file)) unless Dir.exist? File.dirname(path_to(dump_file))
|
53
|
+
|
54
|
+
EasyBackup.logger.info "[PostgreSQL] Dump postgres://#{username}:*****@#{host}:#{port}/#{database}\n#{' '*15}to #{path_to(dump_file)}"
|
55
|
+
|
56
|
+
Open3.popen3 "pg_dump -h #{host} -p #{port} -U #{username} #{database} > #{path_to(dump_file)}" do |i, o, e, t|
|
57
|
+
if t.value.success?
|
58
|
+
if zip_file
|
59
|
+
EasyBackup.logger.info "#{(' '*14)}zip #{path_to(zip_file)}"
|
60
|
+
ZipFile.open(path_to(zip_file), ZipFile::CREATE) { |zip| zip.add dump_file, path_to(dump_file) }
|
61
|
+
end
|
62
|
+
storages.each { |s| s.save path_to(zip_file ? zip_file : dump_file) }
|
63
|
+
else
|
64
|
+
EasyBackup.logger.error "[PostgreSQL] Error: #{e.readlines.join}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
FileUtils.rm path_to(dump_file) if File.exist? path_to(dump_file)
|
69
|
+
FileUtils.rm path_to(zip_file) if zip_file && File.exist?(path_to(zip_file))
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def path_to(file_name)
|
75
|
+
"#{EasyBackup.tmp_path}/pg_dump/#{file_name}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module EasyBackup
|
2
|
+
module Adapter
|
3
|
+
class FileSystem
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@folders = []
|
7
|
+
@files = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def folder(path)
|
11
|
+
@folders << path
|
12
|
+
end
|
13
|
+
|
14
|
+
def folders
|
15
|
+
@folders.map { |f| f.is_a?(Proc) ? f.call : f }
|
16
|
+
end
|
17
|
+
|
18
|
+
def file(file_name)
|
19
|
+
@files << file_name
|
20
|
+
end
|
21
|
+
|
22
|
+
def files
|
23
|
+
@files.map { |f| f.is_a?(Proc) ? f.call : f }
|
24
|
+
end
|
25
|
+
|
26
|
+
def zip(file_name)
|
27
|
+
@zip_file = file_name
|
28
|
+
end
|
29
|
+
|
30
|
+
def zip_file
|
31
|
+
@zip_file.is_a?(Proc) ? @zip_file.call : @zip_file
|
32
|
+
end
|
33
|
+
|
34
|
+
def send_to(storages)
|
35
|
+
if zip_file
|
36
|
+
EasyBackup.logger.info "[FileSystem] Zip #{zip_path(zip_file)}"
|
37
|
+
FileUtils.mkpath File.dirname(zip_path(zip_file))
|
38
|
+
ZipFile.open(zip_path(zip_file), ZipFile::CREATE) do |zip|
|
39
|
+
(files | folders).each do |resource|
|
40
|
+
if Dir.exist? resource
|
41
|
+
EasyBackup.logger.info "#{' '*13}add #{resource}"
|
42
|
+
Dir["#{resource}/**/**"].each do |r|
|
43
|
+
EasyBackup.logger.info "#{' '*13}add #{r}" unless Dir.exist? r
|
44
|
+
zip.add "#{File.basename(resource)}/#{r.gsub("#{resource}/", '')}", r
|
45
|
+
end
|
46
|
+
else
|
47
|
+
EasyBackup.logger.info "#{' '*13}add #{resource}"
|
48
|
+
zip.add File.basename(resource), resource
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
storages.each { |s| s.save zip_path(zip_file) }
|
53
|
+
FileUtils.rm zip_path(zip_file)
|
54
|
+
else
|
55
|
+
(files | folders).each do |resource|
|
56
|
+
storages.each { |s| s.save resource } if File.exist? resource
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def save(resource)
|
62
|
+
folders.each do |folder|
|
63
|
+
EasyBackup.logger.info "[FileSystem] Saving #{resource}\n#{' '*15}into #{folder}"
|
64
|
+
|
65
|
+
FileUtils.mkpath folder unless Dir.exist? folder
|
66
|
+
if Dir.exist? resource
|
67
|
+
FileUtils.cp_r resource, "#{folder}/#{File.basename(resource)}"
|
68
|
+
else
|
69
|
+
FileUtils.cp resource, "#{folder}/#{File.basename(resource)}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def zip_path(file_name)
|
77
|
+
"#{EasyBackup.tmp_path}/zip/#{file_name}"
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'net/sftp'
|
2
|
+
|
3
|
+
module EasyBackup
|
4
|
+
module Adapter
|
5
|
+
class SFTP
|
6
|
+
def host(host=nil)
|
7
|
+
host ? @host = host : @host
|
8
|
+
end
|
9
|
+
|
10
|
+
def username(username=nil)
|
11
|
+
username ? @username = username : @username
|
12
|
+
end
|
13
|
+
|
14
|
+
def password(password=nil)
|
15
|
+
password ? @password = password : @password
|
16
|
+
end
|
17
|
+
|
18
|
+
def folder(folder=nil)
|
19
|
+
if folder
|
20
|
+
@folder = folder
|
21
|
+
else
|
22
|
+
@folder.is_a?(Proc) ? @folder.call : @folder
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def save(resource)
|
27
|
+
Net::SFTP.start(host, username, :password => password) do |sftp|
|
28
|
+
EasyBackup.logger.info "[SFTP] Saving #{resource}\n#{' '*9}into #{host} | #{username} | #{folder}"
|
29
|
+
|
30
|
+
sftp.mkpath! folder
|
31
|
+
sftp.upload! resource, "#{folder}/#{File.basename(resource)}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rufus-scheduler'
|
2
|
+
|
3
|
+
module EasyBackup
|
4
|
+
|
5
|
+
class Base
|
6
|
+
|
7
|
+
def initialize(interval=EasyBackup.interval, &block)
|
8
|
+
@configurations = {}
|
9
|
+
instance_eval &block
|
10
|
+
@scheduler = Rufus::Scheduler.start_new frequency: interval
|
11
|
+
schedule
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](name)
|
15
|
+
@configurations[name]
|
16
|
+
end
|
17
|
+
|
18
|
+
def start
|
19
|
+
@scheduler.join
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def []=(name, value)
|
25
|
+
@configurations[name] = value
|
26
|
+
end
|
27
|
+
|
28
|
+
def config(name=:default, &block)
|
29
|
+
self[name] = Configuration.new do
|
30
|
+
instance_eval &block
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def schedule
|
35
|
+
@configurations.each do |name, c|
|
36
|
+
c.frequencies.each do |f|
|
37
|
+
@scheduler.every f.interval, first_at: f.from do
|
38
|
+
EasyBackup.logger.info "[EasyBackup] Starting #{name} at #{Time.now}"
|
39
|
+
Runner.run c
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module EasyBackup
|
2
|
+
class Configuration
|
3
|
+
attr_reader :resources, :storages, :frequencies
|
4
|
+
|
5
|
+
def initialize(&block)
|
6
|
+
@resources = []
|
7
|
+
@storages = []
|
8
|
+
@frequencies = []
|
9
|
+
instance_eval &block if block_given?
|
10
|
+
end
|
11
|
+
|
12
|
+
def save(adapter_class, &block)
|
13
|
+
resources << adapter_class.new
|
14
|
+
resources.last.instance_eval &block if block_given?
|
15
|
+
end
|
16
|
+
|
17
|
+
def into(adapter_class, &block)
|
18
|
+
storages << adapter_class.new
|
19
|
+
storages.last.instance_eval &block if block_given?
|
20
|
+
end
|
21
|
+
|
22
|
+
def every(interval, options={})
|
23
|
+
frequencies << Frequency.new(interval, options)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Net
|
2
|
+
module SFTP
|
3
|
+
class Session
|
4
|
+
def mkpath!(path)
|
5
|
+
path.split('/').inject(nil) do |previous, relative|
|
6
|
+
current = previous ? "#{previous}/#{relative}" : relative
|
7
|
+
mkdir! current if dir.glob('.', current).empty?
|
8
|
+
current
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'chronic'
|
2
|
+
|
3
|
+
module EasyBackup
|
4
|
+
class Frequency
|
5
|
+
attr_accessor :interval, :from
|
6
|
+
|
7
|
+
def initialize(interval, options={})
|
8
|
+
@interval = interval
|
9
|
+
@from = options[:from].is_a?(String) ? Chronic.parse(options[:from]) : options[:from]
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
data/lib/easy_backup.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'active_support/all'
|
3
|
+
|
4
|
+
require 'easy_backup/version'
|
5
|
+
require 'easy_backup/base'
|
6
|
+
require 'easy_backup/configuration'
|
7
|
+
require 'easy_backup/frequency'
|
8
|
+
require 'easy_backup/runner'
|
9
|
+
|
10
|
+
require 'easy_backup/adapter/file_system'
|
11
|
+
require 'easy_backup/adapter/sftp'
|
12
|
+
require 'easy_backup/adapter/db/postgre_sql'
|
13
|
+
|
14
|
+
require 'easy_backup/extension/net_sftp_session'
|
15
|
+
|
16
|
+
|
17
|
+
include EasyBackup
|
18
|
+
include EasyBackup::Adapter
|
19
|
+
include EasyBackup::Adapter::Db
|
20
|
+
|
21
|
+
module EasyBackup
|
22
|
+
|
23
|
+
def self.logger
|
24
|
+
@@logger ||= Logger.new($stdout)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.logger=(logger)
|
28
|
+
@@logger = logger
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.interval
|
32
|
+
@@interval ||= 1.minute
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.interval=(interval)
|
36
|
+
@@interval = interval
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.tmp_path
|
40
|
+
@tmp_path ||= "#{ENV['tmp'].gsub('\\', '/')}/easy_backup"
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.tmp_path=(path)
|
44
|
+
@tmp_path = path
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.config(name=:default, &block)
|
48
|
+
Base.new do
|
49
|
+
config name, &block
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.load(config_file)
|
54
|
+
Base.new do
|
55
|
+
eval File.open(config_file, 'r') { |f| f.readlines.join("\n") }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
data/spec/config.yaml
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'JSON'
|
3
|
+
require 'yaml'
|
4
|
+
require 'sequel'
|
5
|
+
require 'zip/zip'
|
6
|
+
require 'net/sftp'
|
7
|
+
|
8
|
+
describe Runner, '-> Execution' do
|
9
|
+
|
10
|
+
after :all do
|
11
|
+
FileUtils.rm_rf BACKUP_PATH
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'Backup a file to file system' do
|
15
|
+
config = Configuration.new do
|
16
|
+
save FileSystem do
|
17
|
+
file "#{DATA_PATH}/sample.json"
|
18
|
+
end
|
19
|
+
into FileSystem do
|
20
|
+
folder "#{BACKUP_PATH}/#{Time.now.strftime('%Y%m%d%H%M%S%L')}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
Runner.run config
|
25
|
+
|
26
|
+
file = "#{config.storages.first.folders.first}/sample.json"
|
27
|
+
File.exist?(file).should be_true
|
28
|
+
data = File.open(file, 'r') { |f| JSON.parse f.readlines.join }
|
29
|
+
data['id'].should be 1234
|
30
|
+
data['name'].should eq 'sample'
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'Backup folder to file system' do
|
34
|
+
config = Configuration.new do
|
35
|
+
save FileSystem do
|
36
|
+
folder "#{DATA_PATH}/txt"
|
37
|
+
end
|
38
|
+
into FileSystem do
|
39
|
+
folder "#{BACKUP_PATH}/#{Time.now.strftime('%Y%m%d%H%M%S%L')}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
Runner.run config
|
44
|
+
|
45
|
+
path = "#{config.storages.first.folders.first}/txt"
|
46
|
+
(1..2).each do |i|
|
47
|
+
file = "#{path}/#{i}/text#{i}.txt"
|
48
|
+
File.exist?(file).should be_true
|
49
|
+
File.open(file, 'r') { |f| f.gets.should eq "Text file #{i}" }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'Backup file and folder zipped to file system' do
|
54
|
+
zip_file = "data_#{Time.now.strftime('%Y%m%d%H%M%S%L')}.zip"
|
55
|
+
backup_path = "#{BACKUP_PATH}/#{Time.now.strftime('%Y%m%d%H%M%S%L')}"
|
56
|
+
|
57
|
+
config = Configuration.new do
|
58
|
+
save FileSystem do
|
59
|
+
folder "#{DATA_PATH}/txt"
|
60
|
+
folder "#{DATA_PATH}/sample.json"
|
61
|
+
zip zip_file
|
62
|
+
end
|
63
|
+
into FileSystem do
|
64
|
+
folder backup_path
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
Runner.run config
|
69
|
+
|
70
|
+
File.exists?("#{backup_path}/#{zip_file}").should be_true
|
71
|
+
|
72
|
+
Zip::ZipFile.open("#{backup_path}/#{zip_file}") do |zip|
|
73
|
+
zip.find_entry('sample.json').should_not be_nil
|
74
|
+
zip.find_entry('txt/1/text1.txt').should_not be_nil
|
75
|
+
zip.find_entry('txt/2/text2.txt').should_not be_nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'Backup PostgreSQL to file system' do
|
80
|
+
PostgreSQLHelper.create_db
|
81
|
+
db = PostgreSQLHelper.configuration
|
82
|
+
backup_path = "#{BACKUP_PATH}/#{Time.now.strftime('%Y%m%d%H%M%S%L')}"
|
83
|
+
|
84
|
+
config = Configuration.new do
|
85
|
+
save PostgreSQL do
|
86
|
+
host db['host']
|
87
|
+
database db['database']
|
88
|
+
port db['port']
|
89
|
+
username db['username']
|
90
|
+
password db['password']
|
91
|
+
zip
|
92
|
+
end
|
93
|
+
into FileSystem do
|
94
|
+
folder backup_path
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
Runner.run config
|
99
|
+
|
100
|
+
PostgreSQLHelper.drop_db
|
101
|
+
|
102
|
+
Dir["#{backup_path}/#{db['database']}_*.zip"].should have(1).items
|
103
|
+
Zip::ZipFile.foreach(Dir["#{backup_path}/#{db['database']}_*.zip"].first) do |entry|
|
104
|
+
entry.get_input_stream.read.should include 'PostgreSQL database dump'
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'Backup a file to sftp' do
|
109
|
+
storage = ConfigHelper.get 'sftp'
|
110
|
+
backup_path = "tmp/#{Time.now.strftime('%Y%m%d%H%M%S%L')}"
|
111
|
+
config = Configuration.new do
|
112
|
+
save FileSystem do
|
113
|
+
file "#{DATA_PATH}/txt/1/text1.txt"
|
114
|
+
end
|
115
|
+
into SFTP do
|
116
|
+
host storage['host']
|
117
|
+
username storage['username']
|
118
|
+
password storage['password']
|
119
|
+
folder backup_path
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
Runner.run config
|
124
|
+
|
125
|
+
Net::SFTP.start(storage['host'], storage['username'], :password => storage['password']) do |sftp|
|
126
|
+
sftp.dir.glob(backup_path, 'text1.txt').should have(1).item
|
127
|
+
sftp.file.open("#{backup_path}/text1.txt", 'r') { |f| f.gets.should eq 'Text file 1' }
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EasyBackup, '-> Scheduled run' do
|
4
|
+
|
5
|
+
after :all do
|
6
|
+
FileUtils.rm_rf BACKUP_PATH
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'Run scheduled backup' do
|
10
|
+
test_path = "#{BACKUP_PATH}/#{Time.now.to_f}"
|
11
|
+
|
12
|
+
EasyBackup::Base.new 0.5 do
|
13
|
+
config :test_backup do
|
14
|
+
save FileSystem do
|
15
|
+
file "#{DATA_PATH}/txt/1/text1.txt"
|
16
|
+
end
|
17
|
+
into FileSystem do
|
18
|
+
folder lambda { "#{test_path}/#{Time.now.strftime('%Y-%m-%d %H_%M_%S_%L')}" }
|
19
|
+
end
|
20
|
+
every 0.5, from: 1.second.from_now
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
sleep(2)
|
25
|
+
|
26
|
+
Dir.glob("#{test_path}/*").should have(2).items
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
config :test_backup do
|
2
|
+
|
3
|
+
save PostgreSQL do
|
4
|
+
host 'localhost'
|
5
|
+
database 'test_db'
|
6
|
+
username 'user'
|
7
|
+
password 'password'
|
8
|
+
end
|
9
|
+
|
10
|
+
save FileSystem do
|
11
|
+
folder 'c:/data'
|
12
|
+
end
|
13
|
+
|
14
|
+
into FileSystem do
|
15
|
+
folder 'c:/backup'
|
16
|
+
end
|
17
|
+
|
18
|
+
every 1.day, from: 'today at 22:30'
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Text file 1
|
@@ -0,0 +1 @@
|
|
1
|
+
Text file 2
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'easy_backup'
|
4
|
+
|
5
|
+
DATA_PATH = "#{File.dirname(__FILE__)}/files/data"
|
6
|
+
BACKUP_PATH = "#{ENV['tmp'].gsub('\\', '/')}/easy_backup/backups"
|
7
|
+
|
8
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Configuration, '-> Specification' do
|
4
|
+
|
5
|
+
it 'Save file system' do
|
6
|
+
config = Configuration.new
|
7
|
+
|
8
|
+
config.resources.should be_empty
|
9
|
+
|
10
|
+
config.save FileSystem do
|
11
|
+
folder 'c:/folder1'
|
12
|
+
file 'c:/folder2/file.txt'
|
13
|
+
end
|
14
|
+
|
15
|
+
config.save FileSystem do
|
16
|
+
folder 'c:/folder3'
|
17
|
+
folder 'c:/folder4'
|
18
|
+
folder 'c:/folder5'
|
19
|
+
end
|
20
|
+
|
21
|
+
config.resources.should have(2).items
|
22
|
+
|
23
|
+
config.resources[0].should be_a FileSystem
|
24
|
+
config.resources[0].folders.should have(1).items
|
25
|
+
config.resources[0].folders.should include 'c:/folder1'
|
26
|
+
config.resources[0].files.should have(1).items
|
27
|
+
config.resources[0].files.should include 'c:/folder2/file.txt'
|
28
|
+
|
29
|
+
config.resources[1].should be_a FileSystem
|
30
|
+
config.resources[1].folders.should have(3).items
|
31
|
+
config.resources[1].folders.should include 'c:/folder3'
|
32
|
+
config.resources[1].folders.should include 'c:/folder4'
|
33
|
+
config.resources[1].folders.should include 'c:/folder5'
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'Save postgre sql' do
|
37
|
+
config = Configuration.new
|
38
|
+
|
39
|
+
config.resources.should be_empty
|
40
|
+
|
41
|
+
config.save PostgreSQL do
|
42
|
+
host '192.168.0.0'
|
43
|
+
database 'db0'
|
44
|
+
username 'user0'
|
45
|
+
password 'password0'
|
46
|
+
end
|
47
|
+
|
48
|
+
config.save PostgreSQL do
|
49
|
+
host '192.168.0.1'
|
50
|
+
database 'db1'
|
51
|
+
username 'user1'
|
52
|
+
password 'password1'
|
53
|
+
end
|
54
|
+
|
55
|
+
config.resources.should have(2).items
|
56
|
+
|
57
|
+
(0..1).each do |i|
|
58
|
+
config.resources[i].should be_a PostgreSQL
|
59
|
+
config.resources[i].host.should eq "192.168.0.#{i}"
|
60
|
+
config.resources[i].database.should eq "db#{i}"
|
61
|
+
config.resources[i].username.should eq "user#{i}"
|
62
|
+
config.resources[i].password.should eq "password#{i}"
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'In file system' do
|
68
|
+
config = Configuration.new
|
69
|
+
|
70
|
+
config.storages.should be_empty
|
71
|
+
|
72
|
+
config.into FileSystem do
|
73
|
+
folder 'c:/backup'
|
74
|
+
end
|
75
|
+
|
76
|
+
config.into FileSystem do
|
77
|
+
folder 'c:/other_backup'
|
78
|
+
end
|
79
|
+
|
80
|
+
config.storages.should have(2).items
|
81
|
+
config.storages[0].folders[0].should eq 'c:/backup'
|
82
|
+
config.storages[1].folders[0].should eq 'c:/other_backup'
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'Every day at 22:30 and weekly at 03:00' do
|
86
|
+
config = Configuration.new
|
87
|
+
|
88
|
+
config.frequencies.should be_empty
|
89
|
+
|
90
|
+
config.every 1.day, from: 'today at 22:30'
|
91
|
+
config.every 1.week, from: 'today at 03:00'
|
92
|
+
|
93
|
+
config.frequencies.should have(2).items
|
94
|
+
config.frequencies[0].interval.should eq 1.day
|
95
|
+
config.frequencies[0].from.should eq Time.local(Time.now.year, Time.now.month, Time.now.day, 22, 30)
|
96
|
+
config.frequencies[1].interval.should eq 1.week
|
97
|
+
config.frequencies[1].from.should eq Time.local(Time.now.year, Time.now.month, Time.now.day, 3)
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EasyBackup, '-> Specification' do
|
4
|
+
|
5
|
+
it 'Simple configuration' do
|
6
|
+
backup = EasyBackup.config do
|
7
|
+
save PostgreSQL do
|
8
|
+
host 'localhost'
|
9
|
+
database 'test_db'
|
10
|
+
username 'user'
|
11
|
+
password 'password'
|
12
|
+
end
|
13
|
+
|
14
|
+
save FileSystem do
|
15
|
+
folder 'c:/data'
|
16
|
+
end
|
17
|
+
|
18
|
+
into FileSystem do
|
19
|
+
folder 'c:/backup'
|
20
|
+
end
|
21
|
+
|
22
|
+
every 1.day, from: 'today at 22:30'
|
23
|
+
end
|
24
|
+
|
25
|
+
backup[:default].should be_a Configuration
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'Configure from file' do
|
29
|
+
backup = EasyBackup.load "#{File.dirname(__FILE__)}/../files/config/backup_config.rb"
|
30
|
+
|
31
|
+
backup[:test_backup].should be_a Configuration
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FileSystem, '-> Specification' do
|
4
|
+
|
5
|
+
it 'Append folders' do
|
6
|
+
fs = FileSystem.new
|
7
|
+
fs.folder 'c:/folder1'
|
8
|
+
fs.folder 'c:/folder2'
|
9
|
+
|
10
|
+
fs.folders.should have(2).items
|
11
|
+
fs.folders.should include('c:/folder1')
|
12
|
+
fs.folders.should include('c:/folder2')
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'Append files' do
|
16
|
+
fs = FileSystem.new
|
17
|
+
fs.file 'c:/file1.txt'
|
18
|
+
fs.file 'c:/file2.txt'
|
19
|
+
|
20
|
+
fs.files.should have(2).items
|
21
|
+
fs.files.should include('c:/file1.txt')
|
22
|
+
fs.files.should include('c:/file2.txt')
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'Zip files and folders' do
|
26
|
+
fs = FileSystem.new
|
27
|
+
|
28
|
+
fs.zip_file.should be_nil
|
29
|
+
|
30
|
+
fs.zip 'data.zip'
|
31
|
+
|
32
|
+
fs.zip_file.should eq 'data.zip'
|
33
|
+
|
34
|
+
fs.zip lambda { "#{Time.now}.zip" }
|
35
|
+
|
36
|
+
fs.zip_file.should eq "#{Time.now}.zip"
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PostgreSQL, '-> Specification' do
|
4
|
+
|
5
|
+
it 'Specify database parameters' do
|
6
|
+
db = PostgreSQL.new
|
7
|
+
db.host '192.168.0.1'
|
8
|
+
db.port 1234
|
9
|
+
db.database 'test_db'
|
10
|
+
db.username 'user'
|
11
|
+
db.password 'password'
|
12
|
+
db.dump_file 'db.sql'
|
13
|
+
db.zip
|
14
|
+
|
15
|
+
db.host.should eq '192.168.0.1'
|
16
|
+
db.port.should eq 1234
|
17
|
+
db.database.should eq 'test_db'
|
18
|
+
db.username.should eq 'user'
|
19
|
+
db.password.should eq 'password'
|
20
|
+
db.dump_file.should eq 'db.sql'
|
21
|
+
db.zip_file.should eq 'db.zip'
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'Get database default parameters' do
|
25
|
+
db = PostgreSQL.new
|
26
|
+
|
27
|
+
db.host.should eq 'localhost'
|
28
|
+
db.port.should eq 5432
|
29
|
+
db.database.should be_nil
|
30
|
+
db.username.should eq 'postgres'
|
31
|
+
db.password.should be_nil
|
32
|
+
db.dump_file.should eq "#{db.database}_#{Time.now.strftime('%Y%m%d%H%M%S')}.sql"
|
33
|
+
db.zip_file.should be_nil
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SFTP, '-> Specification' do
|
4
|
+
|
5
|
+
it 'Specify sftp parameters' do
|
6
|
+
sftp = SFTP.new
|
7
|
+
sftp.host '192.168.0.1'
|
8
|
+
sftp.username 'user'
|
9
|
+
sftp.password 'password'
|
10
|
+
sftp.folder '/backup'
|
11
|
+
|
12
|
+
sftp.host.should eq '192.168.0.1'
|
13
|
+
sftp.username.should eq 'user'
|
14
|
+
sftp.password.should eq 'password'
|
15
|
+
sftp.folder.should eq '/backup'
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module PostgreSQLHelper
|
2
|
+
|
3
|
+
def self.configuration
|
4
|
+
if block_given?
|
5
|
+
yield ConfigHelper.get 'postgresql'
|
6
|
+
else
|
7
|
+
ConfigHelper.get 'postgresql'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.create_db
|
12
|
+
configuration do |c|
|
13
|
+
conn = Sequel.connect("postgres://#{c['username']}:#{c['password']}@#{c['host']}:#{c['port']}")
|
14
|
+
conn.run "CREATE DATABASE #{c['database']} WITH ENCODING='UTF8' CONNECTION LIMIT=-1;"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.drop_db
|
19
|
+
configuration do |c|
|
20
|
+
conn = Sequel.connect("postgres://#{c['username']}:#{c['password']}@#{c['host']}:#{c['port']}")
|
21
|
+
conn.run "DROP DATABASE #{c['database']};"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: easy_backup
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Gabriel Naiman
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-06-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rubyzip
|
16
|
+
requirement: &26248488 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *26248488
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: net-sftp
|
27
|
+
requirement: &26248236 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *26248236
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: active_support
|
38
|
+
requirement: &26247984 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *26247984
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: i18n
|
49
|
+
requirement: &26247732 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *26247732
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: chronic
|
60
|
+
requirement: &26247480 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :runtime
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *26247480
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rufus-scheduler
|
71
|
+
requirement: &26247228 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *26247228
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: sequel
|
82
|
+
requirement: &26246976 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *26246976
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: pg
|
93
|
+
requirement: &26246724 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
type: :development
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: *26246724
|
102
|
+
- !ruby/object:Gem::Dependency
|
103
|
+
name: rspec
|
104
|
+
requirement: &26246472 !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: *26246472
|
113
|
+
description: Easy DSL to program backups
|
114
|
+
email:
|
115
|
+
- gabynaiman@gmail.com
|
116
|
+
executables: []
|
117
|
+
extensions: []
|
118
|
+
extra_rdoc_files: []
|
119
|
+
files:
|
120
|
+
- .gitignore
|
121
|
+
- .rspec
|
122
|
+
- Gemfile
|
123
|
+
- README.md
|
124
|
+
- Rakefile
|
125
|
+
- easy_backup.gemspec
|
126
|
+
- lib/easy_backup.rb
|
127
|
+
- lib/easy_backup/adapter/db/postgre_sql.rb
|
128
|
+
- lib/easy_backup/adapter/file_system.rb
|
129
|
+
- lib/easy_backup/adapter/sftp.rb
|
130
|
+
- lib/easy_backup/base.rb
|
131
|
+
- lib/easy_backup/configuration.rb
|
132
|
+
- lib/easy_backup/extension/net_sftp_session.rb
|
133
|
+
- lib/easy_backup/frequency.rb
|
134
|
+
- lib/easy_backup/runner.rb
|
135
|
+
- lib/easy_backup/version.rb
|
136
|
+
- spec/config.yaml
|
137
|
+
- spec/execution/runner_spec.rb
|
138
|
+
- spec/execution/scheduled_spec.rb
|
139
|
+
- spec/files/config/backup_config.rb
|
140
|
+
- spec/files/data/sample.json
|
141
|
+
- spec/files/data/txt/1/text1.txt
|
142
|
+
- spec/files/data/txt/2/text2.txt
|
143
|
+
- spec/spec_helper.rb
|
144
|
+
- spec/specification/configuration_spec.rb
|
145
|
+
- spec/specification/easy_backup_spec.rb
|
146
|
+
- spec/specification/file_system_adapter_spec.rb
|
147
|
+
- spec/specification/postgre_sql_adapter_spec.rb
|
148
|
+
- spec/specification/sftp_adapter_spec.rb
|
149
|
+
- spec/support/helpers/config_helper.rb
|
150
|
+
- spec/support/helpers/postgre_sql_helper.rb
|
151
|
+
homepage: https://github.com/gabynaiman/easy_backup
|
152
|
+
licenses: []
|
153
|
+
post_install_message:
|
154
|
+
rdoc_options: []
|
155
|
+
require_paths:
|
156
|
+
- lib
|
157
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
158
|
+
none: false
|
159
|
+
requirements:
|
160
|
+
- - ! '>='
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: '0'
|
163
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
164
|
+
none: false
|
165
|
+
requirements:
|
166
|
+
- - ! '>='
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
requirements: []
|
170
|
+
rubyforge_project: easy_backup
|
171
|
+
rubygems_version: 1.8.16
|
172
|
+
signing_key:
|
173
|
+
specification_version: 3
|
174
|
+
summary: Easy DSL to program backups
|
175
|
+
test_files: []
|