markmansour-safe 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,33 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../example_helper')
2
+
3
+ describe Astrails::Safe::Stream do
4
+
5
+ before(:each) do
6
+ @parent = Object.new
7
+ @stream = Astrails::Safe::Stream.new(@parent)
8
+ @r = rand(10)
9
+ end
10
+
11
+ def self.it_delegates_to_parent(prop)
12
+ it "delegates #{prop} to parent if not set" do
13
+ mock(@parent).__send__(prop) {@r}
14
+ @stream.send(prop).should == @r
15
+ end
16
+ end
17
+
18
+ def self.it_delegates_to_parent_with_cache(prop)
19
+ it_delegates_to_parent(prop)
20
+
21
+ it "uses cached value for #{prop}" do
22
+ dont_allow(@parent).__send__(prop)
23
+ @stream.instance_variable_set "@#{prop}", @r + 1
24
+ @stream.send(prop).should == @r + 1
25
+ end
26
+ end
27
+
28
+ it_delegates_to_parent_with_cache :id
29
+ it_delegates_to_parent_with_cache :config
30
+
31
+ it_delegates_to_parent :filename
32
+
33
+ end
@@ -0,0 +1,43 @@
1
+ require 'extensions/mktmpdir'
2
+ require 'astrails/safe/tmp_file'
3
+
4
+ require 'astrails/safe/config/node'
5
+ require 'astrails/safe/config/builder'
6
+
7
+ require 'astrails/safe/stream'
8
+
9
+ require 'astrails/safe/source'
10
+ require 'astrails/safe/mysqldump'
11
+ require 'astrails/safe/pgdump'
12
+ require 'astrails/safe/archive'
13
+
14
+ require 'astrails/safe/pipe'
15
+ require 'astrails/safe/gpg'
16
+ require 'astrails/safe/gzip'
17
+
18
+ require 'astrails/safe/sink'
19
+ require 'astrails/safe/local'
20
+ require 'astrails/safe/s3'
21
+
22
+
23
+ module Astrails
24
+ module Safe
25
+ ROOT = File.join(File.dirname(__FILE__), "..", "..")
26
+
27
+ def timestamp
28
+ @timestamp ||= Time.now.strftime("%y%m%d-%H%M")
29
+ end
30
+
31
+ def safe(&block)
32
+ config = Config::Node.new(&block)
33
+ #config.dump
34
+
35
+ Astrails::Safe::Mysqldump.run(config[:mysqldump, :databases])
36
+ Astrails::Safe::Pgdump.run(config[:pgdump, :databases])
37
+ Astrails::Safe::Archive.run(config[:tar, :archives])
38
+
39
+ Astrails::Safe::TmpFile.cleanup
40
+ end
41
+ end
42
+ end
43
+
@@ -0,0 +1,24 @@
1
+ module Astrails
2
+ module Safe
3
+ class Archive < Source
4
+
5
+ def command
6
+ "tar -cf - #{@config[:options]} #{tar_exclude_files} #{tar_files}"
7
+ end
8
+
9
+ def extension; '.tar'; end
10
+
11
+ protected
12
+
13
+ def tar_exclude_files
14
+ [*@config[:exclude]].compact.map{|x| "--exclude=#{x}"} * " "
15
+ end
16
+
17
+ def tar_files
18
+ raise RuntimeError, "missing files for tar" unless @config[:files]
19
+ [*@config[:files]] * " "
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,60 @@
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 pgdump options
7
+ user host port socket skip_tables tar files exclude filename/
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
+ id_or_value = id_or_value.map {|v| v.to_s} if id_or_value.is_a?(Array)
27
+
28
+ # do we have data hash?
29
+ if data = args.shift
30
+ die "#{sym}: hash expected: #{data.inspect}" unless data.is_a?(Hash)
31
+ end
32
+
33
+ #puts "#{sym}: args=#{args.inspect}, id_or_value=#{id_or_value}, data=#{data.inspect}, block=#{block.inspect}"
34
+
35
+ die "#{sym}: unexpected: #{args.inspect}" unless args.empty?
36
+ die "#{sym}: missing arguments" unless id_or_value || data || block
37
+
38
+ if COLLECTIONS.include?(sym.to_s) && id_or_value
39
+ data ||= {}
40
+ end
41
+
42
+ if !data && !block
43
+ # simple value assignment
44
+ @node[sym] = id_or_value
45
+
46
+ elsif id_or_value
47
+ # collection element with id => create collection node and a subnode in it
48
+ key = sym.to_s + "s"
49
+ collection = @node[key] || @node.set(key, {})
50
+ collection.set(id_or_value, data || {}, &block)
51
+
52
+ else
53
+ # simple subnode
54
+ @node.set(sym, data || {}, &block)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,67 @@
1
+ require 'astrails/safe/config/builder'
2
+ module Astrails
3
+ module Safe
4
+ module Config
5
+ class Node
6
+ attr_reader :parent
7
+ def initialize(parent = nil, data = {}, &block)
8
+ @parent, @data = parent, {}
9
+ data.each { |k, v| self[k] = v }
10
+ Builder.new(self).instance_eval(&block) if block
11
+ end
12
+
13
+ # looks for the path from this node DOWN. will not delegate to parent
14
+ def get(*path)
15
+ key = path.shift
16
+ value = @data[key.to_s]
17
+ return value if value && path.empty?
18
+
19
+ value && value.get(*path)
20
+ end
21
+
22
+ # recursive find
23
+ # starts at the node and continues to the parent
24
+ def find(*path)
25
+ get(*path) || @parent && @parent.find(*path)
26
+ end
27
+ alias :[] :find
28
+
29
+ def set(key, value, &block)
30
+ @data[key.to_s] =
31
+ if value.is_a?(Hash)
32
+ Node.new(self, value, &block)
33
+ else
34
+ raise(ArgumentError, "#{key}: no block supported for simple values") if block
35
+ value
36
+ end
37
+ end
38
+ alias :[]= :set
39
+
40
+ def each(&block)
41
+ @data.each(&block)
42
+ end
43
+ include Enumerable
44
+
45
+ def to_hash
46
+ @data.keys.inject({}) do |res, key|
47
+ value = @data[key]
48
+ res[key] = value.is_a?(Node) ? value.to_hash : value
49
+ res
50
+ end
51
+ end
52
+
53
+ def dump(indent = "")
54
+ @data.each do |key, value|
55
+ if value.is_a?(Node)
56
+ puts "#{indent}#{key}:"
57
+ value.dump(indent + " ")
58
+ else
59
+ puts "#{indent}#{key}: #{value.inspect}"
60
+ end
61
+ end
62
+ end
63
+
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,42 @@
1
+ module Astrails
2
+ module Safe
3
+ class Gpg < Pipe
4
+
5
+ def compressed?
6
+ active? || @parent.compressed?
7
+ end
8
+
9
+ protected
10
+
11
+ def pipe
12
+ if key
13
+ rise RuntimeError, "can't use both gpg password and pubkey" if password
14
+ "|gpg -e -r #{key}"
15
+ elsif password
16
+ "|gpg -c --passphrase-file #{gpg_password_file(password)}"
17
+ end
18
+ end
19
+
20
+ def extension
21
+ ".gpg" if active?
22
+ end
23
+
24
+ def active?
25
+ password || key
26
+ end
27
+
28
+ def password
29
+ @password ||= config[:gpg, :password]
30
+ end
31
+
32
+ def key
33
+ @key ||= config[:gpg, :key]
34
+ end
35
+
36
+ def gpg_password_file(pass)
37
+ return "TEMP_GENERATED_FILENAME" if $DRY_RUN
38
+ Astrails::Safe::TmpFile.create("gpg-pass") { |file| file.write(pass) }
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,25 @@
1
+ module Astrails
2
+ module Safe
3
+ class Gzip < Pipe
4
+
5
+ def compressed?
6
+ true
7
+ end
8
+
9
+ protected
10
+
11
+ def pipe
12
+ "|gzip" if active?
13
+ end
14
+
15
+ def extension
16
+ ".gz" if active?
17
+ end
18
+
19
+ def active?
20
+ !@parent.compressed?
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,54 @@
1
+ module Astrails
2
+ module Safe
3
+ class Local < Sink
4
+
5
+ def open(&block)
6
+ return @parent.open(&block) unless active?
7
+ run
8
+ File.open(path, &block) unless $DRY_RUN
9
+ end
10
+
11
+ protected
12
+
13
+ def active?
14
+ # S3 can't upload from pipe. it needs to know file size, so we must pass through :local
15
+ # will change once we add SSH sink
16
+ true
17
+ end
18
+
19
+ def prefix
20
+ @prefix ||= File.expand_path(expand(@config[:local, :path] || raise(RuntimeError, "missing :local/:path in configuration")))
21
+ end
22
+
23
+ def command
24
+ "#{@parent.command} > #{path}"
25
+ end
26
+
27
+ def save
28
+ puts "command: #{command}" if $_VERBOSE
29
+ unless $DRY_RUN
30
+ FileUtils.mkdir_p(prefix) unless File.directory?(prefix)
31
+ system command
32
+ end
33
+ end
34
+
35
+ def cleanup
36
+ return unless keep = @config[:keep, :local]
37
+
38
+ base = File.basename(filename).split(".").first
39
+
40
+ pattern = File.join(prefix, "#{base}*")
41
+ puts "listing files #{pattern.inspect}" if $_VERBOSE
42
+ files = Dir[pattern] .
43
+ select{|f| File.file?(f)} .
44
+ sort
45
+
46
+ cleanup_with_limit(files, keep) do |f|
47
+ puts "removing local file #{f}" if $DRY_RUN || $_VERBOSE
48
+ File.unlink(f) unless $DRY_RUN
49
+ end
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,31 @@
1
+ module Astrails
2
+ module Safe
3
+ class Mysqldump < Source
4
+
5
+ def command
6
+ @commanbd ||= "mysqldump --defaults-extra-file=#{mysql_password_file} #{@config[:options]} #{mysql_skip_tables} #{@id}"
7
+ end
8
+
9
+ def extension; '.sql'; end
10
+
11
+ protected
12
+
13
+ def mysql_password_file
14
+ Astrails::Safe::TmpFile.create("mysqldump") do |file|
15
+ file.puts "[mysqldump]"
16
+ %w/user password socket host port/.each do |k|
17
+ v = @config[k]
18
+ file.puts "#{k} = #{v}" if v
19
+ end
20
+ end
21
+ end
22
+
23
+ def mysql_skip_tables
24
+ if skip_tables = @config[:skip_tables]
25
+ [*skip_tables].map { |t| "--ignore-table=#{@id}.#{t}" } * " "
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,43 @@
1
+ module Astrails
2
+ module Safe
3
+ class Pgdump < Source
4
+
5
+ def command
6
+ @commanbd ||= "pg_dump #{postgres_options} #{postgres_username} #{postgres_password} #{postgres_host} #{postgres_port} #{@id}"
7
+ # @commanbd ||= "pg_dump -U #{@config["user"]} #{@config[:option]} #{@config["database"]} -f #{filename}"
8
+ # @commanbd ||= "mysqldump --defaults-extra-file=#{mysql_password_file} #{@config[:options]} #{mysql_skip_tables} #{@id}"
9
+ end
10
+
11
+ def extension; '.sql'; end
12
+
13
+ protected
14
+
15
+ def postgres_options
16
+ @config[:options]
17
+ end
18
+
19
+ def postgres_host
20
+ @config["host"] ? "--host='#{@config["port"]}'" : ""
21
+ end
22
+
23
+ def postgres_port
24
+ @config["port"] ? "--port='#{@config["port"]}'" : ""
25
+ end
26
+
27
+ def postgres_username
28
+ @config["user"] ? "--username='#{@config["user"]}'" : ""
29
+ end
30
+
31
+ def postgres_password
32
+ `export PGPASSWORD=#{@config["password"]}` if @config["password"]
33
+ end
34
+
35
+ def postgres_skip_tables
36
+ if skip_tables = @config[:skip_tables]
37
+ [*skip_tables].map { |t| "--exclude-table=#{@id}.#{t}" } * " "
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,15 @@
1
+ module Astrails
2
+ module Safe
3
+ class Pipe < Stream
4
+
5
+ def command
6
+ "#{@parent.command}#{pipe}"
7
+ end
8
+
9
+ def filename
10
+ "#{@parent.filename}#{extension}"
11
+ end
12
+
13
+ end
14
+ end
15
+ end