astrails-safe 0.0.7 → 0.0.8
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/lib/astrails/safe.rb +26 -0
- data/lib/astrails/safe/archive.rb +22 -0
- data/lib/astrails/safe/config/builder.rb +58 -0
- data/lib/astrails/safe/config/node.rb +58 -0
- data/lib/astrails/safe/engine.rb +133 -0
- data/lib/astrails/safe/mysqldump.rb +33 -0
- data/lib/astrails/safe/stream.rb +65 -0
- data/lib/astrails/safe/tmp_file.rb +21 -0
- data/safe.gemspec +13 -4
- data/templates/script.rb +106 -0
- metadata +10 -1
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'astrails/safe/tmp_file'
|
2
|
+
require 'astrails/safe/config/node'
|
3
|
+
require 'astrails/safe/config/builder'
|
4
|
+
require 'astrails/safe/stream'
|
5
|
+
require 'astrails/safe/engine'
|
6
|
+
require 'astrails/safe/mysqldump'
|
7
|
+
require 'astrails/safe/archive'
|
8
|
+
|
9
|
+
module Astrails
|
10
|
+
module Safe
|
11
|
+
ROOT = File.join(File.dirname(__FILE__), "..", "..")
|
12
|
+
|
13
|
+
def timestamp
|
14
|
+
@timestamp ||= Time.now.strftime("%y%m%d-%H%M")
|
15
|
+
end
|
16
|
+
|
17
|
+
def safe(&block)
|
18
|
+
config = Config::Node.new(&block)
|
19
|
+
#config.dump
|
20
|
+
|
21
|
+
Astrails::Safe::Mysqldump.run(config[:mysqldump, :databases], timestamp)
|
22
|
+
Astrails::Safe::Archive.run(config[:tar, :archives], timestamp)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Astrails
|
2
|
+
module Safe
|
3
|
+
class Archive < Engine
|
4
|
+
|
5
|
+
def command
|
6
|
+
"tar -cf - #{@config[:options]} #{tar_exclude_files} #{tar_files}"
|
7
|
+
end
|
8
|
+
|
9
|
+
protected
|
10
|
+
|
11
|
+
def tar_exclude_files
|
12
|
+
[*@config[:exclude]].compact.map{|x| "--exclude=#{x}"} * " "
|
13
|
+
end
|
14
|
+
|
15
|
+
def tar_files
|
16
|
+
raise RuntimeError, "missing files for tar" unless @config[:files]
|
17
|
+
[*@config[:files]] * " "
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Astrails
|
2
|
+
module Safe
|
3
|
+
module Config
|
4
|
+
class Builder
|
5
|
+
COLLECTIONS = %w/database archive/
|
6
|
+
ITEMS = %w/s3 key secret bucket path gpg password keep local mysqldump options
|
7
|
+
user socket tar files exclude/
|
8
|
+
NAMES = COLLECTIONS + ITEMS
|
9
|
+
def initialize(node)
|
10
|
+
@node = node
|
11
|
+
end
|
12
|
+
|
13
|
+
# supported args:
|
14
|
+
# args = [value]
|
15
|
+
# args = [id, data]
|
16
|
+
# args = [data]
|
17
|
+
# id/value - simple values, data - hash
|
18
|
+
def method_missing(sym, *args, &block)
|
19
|
+
return super unless NAMES.include?(sym.to_s)
|
20
|
+
|
21
|
+
# do we have id or value?
|
22
|
+
unless args.first.is_a?(Hash)
|
23
|
+
id_or_value = args.shift # nil for args == []
|
24
|
+
end
|
25
|
+
|
26
|
+
# do we have data hash?
|
27
|
+
if data = args.shift
|
28
|
+
die "#{sym}: hash expected: #{data.inspect}" unless data.is_a?(Hash)
|
29
|
+
end
|
30
|
+
|
31
|
+
#puts "#{sym}: args=#{args.inspect}, id_or_value=#{id_or_value}, data=#{data.inspect}, block=#{block.inspect}"
|
32
|
+
|
33
|
+
die "#{sym}: unexpected: #{args.inspect}" unless args.empty?
|
34
|
+
die "#{sym}: missing arguments" unless id_or_value || data || block
|
35
|
+
|
36
|
+
if COLLECTIONS.include?(sym.to_s) && id_or_value
|
37
|
+
data ||= {}
|
38
|
+
end
|
39
|
+
|
40
|
+
if !data && !block
|
41
|
+
# simple value assignment
|
42
|
+
@node[sym] = id_or_value
|
43
|
+
|
44
|
+
elsif id_or_value
|
45
|
+
# collection element with id => create collection node and a subnode in it
|
46
|
+
key = sym.to_s + "s"
|
47
|
+
collection = @node[key] || @node.set(key, {})
|
48
|
+
collection.set(id_or_value, data || {}, &block)
|
49
|
+
|
50
|
+
else
|
51
|
+
# simple subnode
|
52
|
+
@node.set(sym, data || {}, &block)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'astrails/safe/config/builder'
|
2
|
+
module Astrails
|
3
|
+
module Safe
|
4
|
+
module Config
|
5
|
+
class Node
|
6
|
+
def initialize(parent = nil, data = {}, &block)
|
7
|
+
@parent, @data = parent, {}
|
8
|
+
data.each { |k, v| self[k] = v }
|
9
|
+
Builder.new(self).instance_eval(&block) if block
|
10
|
+
end
|
11
|
+
|
12
|
+
# looks for the path from this node DOWN. will not delegate to parent
|
13
|
+
def get(*path)
|
14
|
+
key = path.shift
|
15
|
+
value = @data[key.to_s]
|
16
|
+
return value if value && path.empty?
|
17
|
+
|
18
|
+
value && value.get(*path)
|
19
|
+
end
|
20
|
+
|
21
|
+
# recursive find
|
22
|
+
# starts at the node and continues to the parent
|
23
|
+
def find(*path)
|
24
|
+
get(*path) || @parent && @parent.find(*path)
|
25
|
+
end
|
26
|
+
alias :[] :find
|
27
|
+
|
28
|
+
def set(key, value, &block)
|
29
|
+
@data[key.to_s] =
|
30
|
+
if value.is_a?(Hash)
|
31
|
+
Node.new(self, value, &block)
|
32
|
+
else
|
33
|
+
raise(ArgumentError, "#{key}: no block supported for simple values") if block
|
34
|
+
value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
alias :[]= :set
|
38
|
+
|
39
|
+
def each(&block)
|
40
|
+
@data.each(&block)
|
41
|
+
end
|
42
|
+
include Enumerable
|
43
|
+
|
44
|
+
def dump(indent = "")
|
45
|
+
@data.each do |key, value|
|
46
|
+
if value.is_a?(Node)
|
47
|
+
puts "#{indent}#{key}:"
|
48
|
+
value.dump(indent + " ")
|
49
|
+
else
|
50
|
+
puts "#{indent}#{key}: #{value.inspect}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module Astrails
|
2
|
+
module Safe
|
3
|
+
class Engine
|
4
|
+
|
5
|
+
def self.run(config, timestamp)
|
6
|
+
unless config
|
7
|
+
puts "No configuration found for #{kind}"
|
8
|
+
return
|
9
|
+
end
|
10
|
+
config.each do |key, value|
|
11
|
+
new(key, value, timestamp).run
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor :timestamp
|
16
|
+
attr_reader :id, :config
|
17
|
+
def initialize(id, config, timestamp)
|
18
|
+
@config, @id, @timestamp = config, id, timestamp
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
puts "#{kind}: #{@id}" if $_VERBOSE
|
23
|
+
|
24
|
+
stream = Stream.new(@config, command, File.join(path, backup_filename))
|
25
|
+
stream.run # execute backup comand. result is file stream.filename
|
26
|
+
|
27
|
+
# UPLOAD
|
28
|
+
upload(stream.filename)
|
29
|
+
|
30
|
+
# CLEANUP
|
31
|
+
cleanup_s3(stream.filename)
|
32
|
+
cleanup_local(stream.filename)
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def kind
|
38
|
+
self.class.name.split('::').last.downcase
|
39
|
+
end
|
40
|
+
|
41
|
+
def backup_filename
|
42
|
+
@backup_filename ||= "#{kind}-#{id}.#{timestamp}.tar"
|
43
|
+
end
|
44
|
+
|
45
|
+
def expand(path)
|
46
|
+
path.gsub(/:kind\b/, kind).gsub(/:id\b/, id)
|
47
|
+
end
|
48
|
+
|
49
|
+
def s3_path
|
50
|
+
@s3_path ||= expand(@config[:s3, :path] || ":kind/:id")
|
51
|
+
end
|
52
|
+
|
53
|
+
def path
|
54
|
+
@path ||= File.expand_path(expand(@config[:path] || raise(RuntimeError, "missing :path in configuration")))
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def upload(filename)
|
59
|
+
|
60
|
+
bucket = @config[:s3, :bucket]
|
61
|
+
key = @config[:s3, :key]
|
62
|
+
secret = @config[:s3, :secret]
|
63
|
+
|
64
|
+
return unless bucket && key && secret
|
65
|
+
|
66
|
+
upload_path = File.join(s3_path, File.basename(filename))
|
67
|
+
|
68
|
+
puts "Uploading file #{filename} to #{bucket}/#{upload_path}" if $_VERBOSE || $DRY_RUN
|
69
|
+
if $LOCAL
|
70
|
+
puts "skip upload (local operation)"
|
71
|
+
else
|
72
|
+
# needed in cleanup even on dry run
|
73
|
+
AWS::S3::Base.establish_connection!(:access_key_id => key, :secret_access_key => secret, :use_ssl => true)
|
74
|
+
|
75
|
+
unless $DRY_RUN
|
76
|
+
AWS::S3::Bucket.create(bucket)
|
77
|
+
AWS::S3::S3Object.store(s3_path, open(filename), bucket)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
puts "...done" if $_VERBOSE
|
81
|
+
end
|
82
|
+
|
83
|
+
# call block on files to be removed (all except for the LAST 'limit' files
|
84
|
+
def cleanup_files(files, limit, &block)
|
85
|
+
return unless files.size > limit
|
86
|
+
|
87
|
+
to_remove = files[0..(files.size - limit - 1)]
|
88
|
+
to_remove.each(&block)
|
89
|
+
end
|
90
|
+
|
91
|
+
def cleanup_local(filename)
|
92
|
+
return unless keep = @config[:keep, :local]
|
93
|
+
|
94
|
+
dir = File.dirname(filename)
|
95
|
+
base = File.basename(filename).split(".").first
|
96
|
+
|
97
|
+
files = Dir[File.join(dir, "#{base}*")] .
|
98
|
+
select{|f| File.file?(f)} .
|
99
|
+
sort
|
100
|
+
|
101
|
+
cleanup_files(files, keep) do |f|
|
102
|
+
puts "removing local file #{f}" if $DRY_RUN || $_VERBOSE
|
103
|
+
File.unlink(f) unless $DRY_RUN
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def cleanup_s3(filename)
|
108
|
+
|
109
|
+
return unless keep = @config[:keep, :s3]
|
110
|
+
|
111
|
+
bucket = @config[:s3, :bucket]
|
112
|
+
|
113
|
+
base = File.basename(filename).split(".").first
|
114
|
+
|
115
|
+
puts "listing files in #{bucket}:#{s3_path}"
|
116
|
+
files = AWS::S3::Bucket.objects(bucket, :prefix => s3_path, :max_keys => keep * 2)
|
117
|
+
puts files.collect(&:key) if $_VERBOSE
|
118
|
+
|
119
|
+
files = files.
|
120
|
+
collect(&:key).
|
121
|
+
select{|f| File.basename(f)[0..(base.length - 1)] == base}.
|
122
|
+
sort
|
123
|
+
|
124
|
+
cleanup_files(files, keep) do |f|
|
125
|
+
puts "removing s3 file #{bucket}:#{f}" if $DRY_RUN || $_VERBOSE
|
126
|
+
AWS::S3::Bucket.find(bucket)[f].delete unless $DRY_RUN
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Astrails
|
2
|
+
module Safe
|
3
|
+
class Mysqldump < Engine
|
4
|
+
|
5
|
+
def command
|
6
|
+
"mysqldump --defaults-extra-file=#{password_file} #{@config[:options]} #{mysql_skip_tables} #{@id}"
|
7
|
+
end
|
8
|
+
|
9
|
+
protected
|
10
|
+
|
11
|
+
def password_file
|
12
|
+
Astrails::Safe::TmpFile.create("mysqldump") do |file|
|
13
|
+
file.puts "[mysqldump]"
|
14
|
+
%w/user password socket host port/.each do |k|
|
15
|
+
v = @config[k]
|
16
|
+
file.puts "#{k} = #{v}" if v
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def mysql_skip_tables
|
22
|
+
if skip_tables = @config[:skip_tables]
|
23
|
+
[*skip_tables].map { |t| "--ignore-table=#{@id}.#{t}" } * " "
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def mysqldump_extra_options
|
28
|
+
@config[:options] + " " if @config[:options]
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Astrails
|
2
|
+
module Safe
|
3
|
+
class Stream
|
4
|
+
|
5
|
+
attr_accessor :config, :command, :filename
|
6
|
+
def initialize(config, command, filename)
|
7
|
+
@config, @command, @filename = config, command, filename
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
encrypt || compress # use gpg or gzip
|
12
|
+
redirect
|
13
|
+
execute
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def gpg_password_file(pass)
|
19
|
+
Astrails::Safe::TmpFile.create("gpg-pass") { |file| file.write(pass) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def encrypt
|
23
|
+
|
24
|
+
password = @config[:gpg, :password]
|
25
|
+
key = @config[:gpg, :key]
|
26
|
+
|
27
|
+
return false unless key || password
|
28
|
+
|
29
|
+
if key
|
30
|
+
rise RuntimeError, "can't use both gpg password and pubkey" if password
|
31
|
+
|
32
|
+
@filename << ".gpg"
|
33
|
+
@command << "|gpg -e -r #{key}"
|
34
|
+
else
|
35
|
+
@filename << ".gpg"
|
36
|
+
unless $DRY_RUN
|
37
|
+
@command << "|gpg -c --passphrase-file #{gpg_password_file(password)}"
|
38
|
+
else
|
39
|
+
@command << "|gpg -c --passphrase-file TEMP_GENERATED_FILENAME"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
def compress
|
46
|
+
@filename << ".gz"
|
47
|
+
@command << "|gzip"
|
48
|
+
end
|
49
|
+
|
50
|
+
def redirect
|
51
|
+
@command << ">" << @filename
|
52
|
+
end
|
53
|
+
|
54
|
+
def execute
|
55
|
+
dir = File.dirname(filename)
|
56
|
+
FileUtils.mkdir_p(dir) unless File.directory?(dir) || $DRY_RUN
|
57
|
+
|
58
|
+
# EXECUTE
|
59
|
+
puts "Backup command: #{@command}" if $DRY_RUN || $_VERBOSE
|
60
|
+
system @command unless $DRY_RUN
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
module Astrails
|
3
|
+
module Safe
|
4
|
+
module TmpFile
|
5
|
+
@KEEP_FILES = []
|
6
|
+
TMPDIR = Dir.mktmpdir
|
7
|
+
|
8
|
+
def self.create(name)
|
9
|
+
# create temp directory
|
10
|
+
|
11
|
+
file = Tempfile.new(name, TMPDIR)
|
12
|
+
|
13
|
+
yield file
|
14
|
+
|
15
|
+
file.close
|
16
|
+
@KEEP_FILES << file # so that it will not get gcollected and removed from filesystem until the end
|
17
|
+
file.path
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/safe.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "safe"
|
3
|
-
s.version = "0.0.
|
3
|
+
s.version = "0.0.8"
|
4
4
|
s.date = "2009-03-15"
|
5
5
|
s.summary = "Astrails Safe"
|
6
6
|
s.email = "we@astrails.com"
|
@@ -9,9 +9,18 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.has_rdoc = false
|
10
10
|
s.authors = ["Astrails Ltd."]
|
11
11
|
s.files = files = %w(
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
bin/astrails-safe
|
13
|
+
lib/astrails/safe.rb
|
14
|
+
lib/astrails/safe/mysqldump.rb
|
15
|
+
lib/astrails/safe/stream.rb
|
16
|
+
lib/astrails/safe/config/builder.rb
|
17
|
+
lib/astrails/safe/config/node.rb
|
18
|
+
lib/astrails/safe/engine.rb
|
19
|
+
lib/astrails/safe/archive.rb
|
20
|
+
lib/astrails/safe/tmp_file.rb
|
21
|
+
templates/script.rb
|
22
|
+
safe.gemspec
|
23
|
+
)
|
15
24
|
s.executables = files.grep(/^bin/).map {|x| x.gsub(/^bin\//, "")}
|
16
25
|
|
17
26
|
s.test_files = []
|
data/templates/script.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
safe do
|
2
|
+
# global path
|
3
|
+
# supported substitutions:
|
4
|
+
# :kind -> backup 'engine' kind, e.g. "mysqldump" or "archive"
|
5
|
+
# :id -> backup 'id', e.g. "blog", "production", etc.
|
6
|
+
# you can set separate :path for all backups (or once globally here)
|
7
|
+
path "/backup/:kind/:id"
|
8
|
+
|
9
|
+
## uncomment to enable uploads to Amazon S3
|
10
|
+
## Amazon S3 auth (optional)
|
11
|
+
# s3 do
|
12
|
+
# key YOUR_S3_KEY
|
13
|
+
# secret YOUR_S3_SECRET
|
14
|
+
# bucket S3_BUCKET
|
15
|
+
# end
|
16
|
+
|
17
|
+
## alternative style:
|
18
|
+
# s3 :key => YOUR_S3_KEY, :secret => YOUR_S3_SECRET, :bucket => S3_BUCKET
|
19
|
+
|
20
|
+
## uncomment to enable GPG encryption.
|
21
|
+
## Note: you can use public 'key' or symmetric password but not both!
|
22
|
+
# gpg do
|
23
|
+
# # key "backup@astrails.com"
|
24
|
+
# password "astrails"
|
25
|
+
# end
|
26
|
+
|
27
|
+
## uncomment to enable backup rotation. keep only given number of latest
|
28
|
+
## backups. remove the rest
|
29
|
+
# keep do
|
30
|
+
# local 4 # keep 4 local backups
|
31
|
+
# s3 20 # keep 20 S3 backups
|
32
|
+
# end
|
33
|
+
|
34
|
+
# backup mysql databases with mysqldump
|
35
|
+
mysqldump do
|
36
|
+
# you can override any setting from parent in a child:
|
37
|
+
path "/backup/mysql/:id"
|
38
|
+
|
39
|
+
options "-ceKq --single-transaction --create-options"
|
40
|
+
|
41
|
+
user "astrails"
|
42
|
+
password ""
|
43
|
+
# host "localhost"
|
44
|
+
# port 3306
|
45
|
+
socket "/var/run/mysqld/mysqld.sock"
|
46
|
+
|
47
|
+
# database is a 'collection' element. it must have a hash or block parameter
|
48
|
+
# it will be 'collected' in a 'databases', with database id (1st arg) used as hash key
|
49
|
+
# the following code will create mysqldump/databases/blog and mysqldump/databases/mysql ocnfiguration 'nodes'
|
50
|
+
|
51
|
+
# backup database with default values
|
52
|
+
# database :blog
|
53
|
+
|
54
|
+
# backup overriding some values
|
55
|
+
# database :production do
|
56
|
+
# # you can override 'partially'
|
57
|
+
# keep :local => 3
|
58
|
+
# # keep/local is 3, and keep/s3 is 20 (from parent)
|
59
|
+
|
60
|
+
# # local override for gpg password
|
61
|
+
# gpg do
|
62
|
+
# password "custom-production-pass"
|
63
|
+
# end
|
64
|
+
|
65
|
+
# skip_tables [:logger_exceptions, :request_logs] # skip those tables during backup
|
66
|
+
# end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
tar do
|
72
|
+
path "/backup/archives"
|
73
|
+
|
74
|
+
# 'archive' is a collection item, just like 'database'
|
75
|
+
# archive "git-repositories" do
|
76
|
+
# # files and directories to backup
|
77
|
+
# files "/home/git/repositories"
|
78
|
+
# end
|
79
|
+
|
80
|
+
# archive "etc-files" do
|
81
|
+
# files "/etc"
|
82
|
+
# # exlude those files/directories
|
83
|
+
# exclude "/etc/puppet/other"
|
84
|
+
# end
|
85
|
+
|
86
|
+
# archive "dot-configs" do
|
87
|
+
# files "/home/*/.[^.]*"
|
88
|
+
# end
|
89
|
+
|
90
|
+
# archive "blog" do
|
91
|
+
# files "/var/www/blog.astrails.com/"
|
92
|
+
# # specify multiple files/directories as array
|
93
|
+
# exclude ["/var/www/blog.astrails.com/log", "/var/www/blog.astrails.com/tmp"]
|
94
|
+
# end
|
95
|
+
|
96
|
+
# archive "site" do
|
97
|
+
# files "/var/www/astrails.com/"
|
98
|
+
# exclude ["/var/www/astrails.com/log", "/var/www/astrails.com/tmp"]
|
99
|
+
# end
|
100
|
+
|
101
|
+
# archive :misc do
|
102
|
+
# files [ "/backup/*.rb" ]
|
103
|
+
# end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: astrails-safe
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Astrails Ltd.
|
@@ -32,6 +32,15 @@ extra_rdoc_files: []
|
|
32
32
|
|
33
33
|
files:
|
34
34
|
- bin/astrails-safe
|
35
|
+
- lib/astrails/safe.rb
|
36
|
+
- lib/astrails/safe/mysqldump.rb
|
37
|
+
- lib/astrails/safe/stream.rb
|
38
|
+
- lib/astrails/safe/config/builder.rb
|
39
|
+
- lib/astrails/safe/config/node.rb
|
40
|
+
- lib/astrails/safe/engine.rb
|
41
|
+
- lib/astrails/safe/archive.rb
|
42
|
+
- lib/astrails/safe/tmp_file.rb
|
43
|
+
- templates/script.rb
|
35
44
|
- safe.gemspec
|
36
45
|
has_rdoc: false
|
37
46
|
homepage: http://github.com/astrails/safe
|