squidward 0.5

Sign up to get free protection for your applications and to get access to all the features.
data/bin/squidward ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pathname'
4
+ $LOAD_PATH.unshift(File.dirname(Pathname.new(__FILE__).realpath) + '/../')
5
+
6
+ require 'lib/squidward'
7
+ require 'lib/squidward/command'
8
+
9
+ args = ARGV.dup
10
+ ARGV.clear
11
+ command = args.shift.strip rescue 'help'
12
+
13
+ Squidward::Command.run!(command, args)
data/lib/squidward.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ %w{aws/s3 fileutils logger md5 base64 yaml sdbtools pstore}.each &method(:require)
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+ require 'squidward/command'
5
+
6
+ # These are the default constants used along the code to refer to the squidward folders
7
+ CONF_PATH, SETTINGS_FILE, TEMP_PATH, LOG_FILE = "~/.squidward", ".settings", "temp", "squidward.log"
8
+
9
+ # Default module that contains the whole application
10
+ module Squidward
11
+ # Commands container, if you can't call this a command pattern implentation, there's no such thing
12
+ module Command
13
+ class << self
14
+ # Runs the given command and performs the proper invocation based on the user input.
15
+ # This method is the core dispatcher for the whole application.
16
+ def run!(command, args)
17
+ command, method = command.gsub(/-/,'_').split(/:/)
18
+ klass, method = eval("Squidward::Command::#{command.capitalize}"), (method or :index).to_sym
19
+
20
+ instance = klass.new
21
+ instance.send(method, args)
22
+ rescue
23
+ puts("Unknown command. Run 'squidward help' for usage information.")
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ require 'commands/base'
3
+ Dir["#{File.dirname(__FILE__)}/commands/*"].each { |c| require c }
@@ -0,0 +1,107 @@
1
+ module Squidward
2
+ module Command
3
+ # Base command from where all the other commands inherit
4
+ # it contains the most used methods (stout, sterror, etc) and
5
+ # also handles the configuration, hence AWS credentials
6
+ class Base
7
+ # Creates a new instance and prompts the user for the
8
+ # credentials if they aren't stored
9
+ def initialize()
10
+ unless (@credentials = read_credentials)
11
+ @credentials = ask_for_credentials
12
+ store_credentials(@credentials)
13
+ end
14
+ end
15
+
16
+ # Gets the AWS Credentials from the user
17
+ def ask_for_credentials
18
+ puts "Enter your AWS credentials."
19
+
20
+ print "Access Key ID: "
21
+ user = ask
22
+
23
+ print "Secret Access Key: "
24
+ password = ask_for_password
25
+
26
+ return [user, password]
27
+ end
28
+
29
+ # Turns off the standard output while writting the password
30
+ def echo_off
31
+ system "stty -echo"
32
+ end
33
+
34
+ # Turns on the standard output after writting the password
35
+ def echo_on
36
+ system "stty echo"
37
+ end
38
+
39
+ # Gets trimmed input from the user
40
+ def ask
41
+ gets.strip
42
+ end
43
+
44
+ # Gets trimmed input from the user but with echoing off
45
+ def ask_for_password
46
+ echo_off
47
+ password = ask
48
+ puts
49
+ echo_on
50
+ return password
51
+ end
52
+
53
+ # Reads the configuration from the profile, if the reload parameter is set to true, it will
54
+ # read it again, if not it will just return what it's already in-memory.
55
+ def configuration(reload = false)
56
+ # if the user asks not to reload we should send what we've got except we don't got anything
57
+ return @configuration if (@configuration and not reload)
58
+ path = File.expand_path File.join(CONF_PATH, SETTINGS_FILE)
59
+ # if the configuration file isn't created yet it should create it
60
+ return {} unless File.exists?(path)
61
+ @configuration = YAML.load_file(path)
62
+ end
63
+
64
+ # Reads credentials from the configuration file
65
+ def read_credentials
66
+ conf = self.configuration
67
+ return nil unless conf[:credentials]
68
+ return [conf[:credentials][:access_key], conf[:credentials][:secret_key]]
69
+ end
70
+
71
+ # Dumps configuration file contents to the user profile
72
+ def store_configuration(new_configuration)
73
+ FileUtils.mkpath(File.expand_path(CONF_PATH))
74
+ File.open(File.expand_path(File.join(CONF_PATH, SETTINGS_FILE)), "w+") do |io|
75
+ YAML::dump(new_configuration, io)
76
+ end
77
+ end
78
+
79
+ # Stores the users credentials on the configuration.
80
+ def store_credentials(credentials)
81
+ conf = configuration
82
+ conf[:credentials] = {}
83
+ conf[:credentials][:access_key], conf[:credentials][:secret_key] = credentials[0], credentials[1]
84
+ store_configuration(conf)
85
+ end
86
+
87
+ # Nice and clean way of echoing
88
+ def display(msg, newline=true)
89
+ if newline
90
+ puts(msg)
91
+ else
92
+ print(msg)
93
+ STDOUT.flush
94
+ end
95
+ end
96
+
97
+ # Logger singleton
98
+ def logger
99
+ return @logger if @logger
100
+ @logger = Logger.new(File.expand_path(File.join(CONF_PATH, LOG_FILE)))
101
+ @logger.level = Logger::INFO
102
+ @logger.formatter = Proc.new {|s, t, n, msg| "[#{t}] [#{s}] #{msg}\n"}
103
+ @logger
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,13 @@
1
+ module Squidward
2
+ module Command
3
+ # Handles all the command related to AWS S3's buckets
4
+ class Bucket < Base
5
+ # Sets the destination bucket for the backups to be uploaded on S3
6
+ def set(args)
7
+ new_configuration = configuration
8
+ new_configuration[:default_bucket] = args[0]
9
+ store_configuration(new_configuration)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Squidward
2
+ module Command
3
+ # Handles all the commands related to credentials
4
+ class Credentials < Base
5
+ # Removes the stored credentials from the current configuration
6
+ def clear(args = nil)
7
+ new_configuration = configuration
8
+ new_configuration[:credentials] = nil
9
+ store_configuration(new_configuration)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ module Squidward
2
+ module Command
3
+ # Handles everything related to AWS SimpleDB Domains for Backup
4
+ class Domain < Base
5
+ # Configures a SimpleDB domain for backup. When given the second parameter is
6
+ # the virtual path inside the bucket to be stored.
7
+ def backup(args)
8
+ domain, vpath = args
9
+ conf = self.configuration
10
+ conf[:domains] = (conf[:domains] or []) + [{:domain => domain, :vpath => vpath}]
11
+ store_configuration(conf)
12
+ end
13
+
14
+ # Removes a SimpleDB domain already being backed up from the current user profile
15
+ # so it doesn't run anymore.
16
+ def remove(args)
17
+ domain, vpath = args
18
+ conf = self.configuration
19
+ conf[:domains] = conf[:domains].reject{|d| d[:domain].downcase.strip == domain.downcase.strip}
20
+ store_configuration(conf)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,79 @@
1
+ module Squidward
2
+ module Command
3
+ class Help < Base
4
+ class HelpGroup < Array
5
+ attr_reader :title
6
+
7
+ def initialize(title)
8
+ @title = title
9
+ end
10
+
11
+ def command(name, description)
12
+ self << [name, description]
13
+ end
14
+
15
+ def space
16
+ self << ['', '']
17
+ end
18
+ end
19
+
20
+ def self.groups
21
+ @groups ||= []
22
+ end
23
+
24
+ def self.group(title, &block)
25
+ groups << begin
26
+ group = HelpGroup.new(title)
27
+ yield group
28
+ group
29
+ end
30
+ end
31
+
32
+ def self.create_default_groups!
33
+ group 'General Commands' do |group|
34
+ group.command 'help', 'show this usage'
35
+ group.command 'version', 'show gem version'
36
+ group.space
37
+ group.command 'bucket:set <name>', 'set the S3 bucket where the files will be uploaded (default: backup, created when missing)'
38
+ group.space
39
+ group.command 'credentials:clear', 'clear the credentials stored on your settings'
40
+ group.space
41
+ group.command 'domain:backup <domain> [vpath] ', 'add aws_simpledb domain for backup [optional vpath a virtual path inside the bucket]'
42
+ group.command 'domain:remove <domain>', 'remove aws_simpledb domain from backup list'
43
+ group.space
44
+ group.command 'run', 'run the process by taking one by one the domains configured on your settings'
45
+ group.space
46
+ group.command 'info', 'display your current settings'
47
+ group.command 'logs', 'display lastest information from the log file'
48
+ end
49
+ end
50
+
51
+ def index(args)
52
+ display usage
53
+ end
54
+
55
+ def usage
56
+ longest_command_length = self.class.groups.map do |group|
57
+ group.map { |g| g.first.length }
58
+ end.flatten.max
59
+
60
+ self.class.groups.inject(StringIO.new) do |output, group|
61
+ output.puts "=== %s" % group.title
62
+ output.puts
63
+
64
+ group.each do |command, description|
65
+ if command.empty?
66
+ output.puts
67
+ else
68
+ output.puts "%-*s # %s" % [longest_command_length, command, description]
69
+ end
70
+ end
71
+ output.puts
72
+ output
73
+ end.string
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ Squidward::Command::Help.create_default_groups!
@@ -0,0 +1,26 @@
1
+ module Squidward
2
+ module Command
3
+ # Handles the echoing of the current configuration (pretty basic tough)
4
+ class Info < Base
5
+ # Goes thru the settings and echos their values.
6
+ def index(args = nil)
7
+ display("=== Current Settings")
8
+ display("Amazon Web Services Account: #{configuration[:credentials][:access_key]}")
9
+ display("Amazon S3 Bucket: #{configuration[:default_bucket]}")
10
+ display("")
11
+ if (configuration[:domains] and configuration[:domains].size > 0)
12
+ display("Configured Domains: ", false)
13
+ first = true
14
+ configuration[:domains].each do |domain|
15
+ spaces = (not first) ? " " * 30 : ""
16
+ display(spaces + "#{domain[:domain]} is being uploaded to #{domain[:vpath] or "<bucket_root>"}")
17
+ first = false
18
+ end
19
+ else
20
+ display("Configured Domains: You currently have no domains configuration for backup")
21
+ end
22
+ display("")
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ module Squidward
2
+ module Command
3
+ # Handles the information related to the log being genereated by the application
4
+ class Logs < Base
5
+ # Echoes to the stdout the latest 25 lines of the log
6
+ def index(args = nil)
7
+ system "tail -n 25 #{File.expand_path(File.join(CONF_PATH, LOG_FILE))}"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,86 @@
1
+ module Squidward
2
+ module Command
3
+ # Core class of the whole application. It simply runs everything once it has been configured.
4
+ class Run < Base
5
+ def initialize()
6
+ super
7
+ aws_config = {}
8
+ aws_config[:access_key_id], aws_config[:secret_access_key] = @credentials
9
+ AWS::S3::Base.establish_connection!(aws_config)
10
+ end
11
+
12
+ # Runs the backup for each configured simpledb domain
13
+ def index(args = {})
14
+ ensure_bucket!(bucket)
15
+ domains.each do |domain|
16
+ internal_run(domain)
17
+ end
18
+ end
19
+
20
+ # If the configured bucket does not exists on AWS S3 it will create it,
21
+ # else it will just retrieve it.
22
+ def ensure_bucket!(bucket_name)
23
+ begin
24
+ AWS::S3::Bucket.find(bucket_name)
25
+ rescue
26
+ AWS::S3::Bucket.create(bucket_name)
27
+ end
28
+ end
29
+
30
+ # Performs a backup for a specific domain
31
+ def internal_run(pair = {})
32
+ domain_name, virtual_path = pair[:domain], (pair[:vpath] or "")
33
+ return unless database.domain_exists?(pair[:domain])
34
+ dump_file = dump_domain(domain_name)
35
+ dump_file_md5 = Base64.encode64(compute_md5(dump_file).digest)
36
+ upload_to = File.join(virtual_path, generate_dump_filename(domain_name))
37
+ AWS::S3::S3Object.store("#{upload_to}.tar.gz", open(dump_file), bucket, :content_md5 => dump_file_md5)
38
+ File.delete(dump_file)
39
+ end
40
+
41
+ # Dumps the domain contents to YAML file
42
+ def dump_domain(domain_name)
43
+ temp_folder = File.expand_path(File.join(CONF_PATH, TEMP_PATH))
44
+ FileUtils.mkpath(temp_folder)
45
+ temp_file = File.join(temp_folder, generate_dump_filename(domain_name))
46
+ results = database.domain(domain_name).selection().results
47
+ File.open(temp_file, "w+") { |io| YAML::dump(results, io) }
48
+ `tar -czf "#{temp_file}.tar.gz" -C "#{File.dirname(temp_file)}" "#{File.basename(temp_file)}"`
49
+ File.delete(temp_file)
50
+ return "#{temp_file}.tar.gz"
51
+ end
52
+
53
+ # Generates a unique filename for the current domain backup
54
+ def generate_dump_filename(domain_name)
55
+ "#{domain_name}-#{Time.now.utc.strftime("%Y%m%d%H%M%S%Z%Y")}"
56
+ end
57
+
58
+ # Singleton instance of the database
59
+ def database
60
+ @database ||= SDBTools::Database.new(@credentials[0], @credentials[1], :logger => self.logger)
61
+ end
62
+
63
+ # Returns every configured domain for the current user
64
+ def domains
65
+ return self.configuration[:domains]
66
+ end
67
+
68
+ # Returns the bucket configured for the current user
69
+ def bucket
70
+ return (self.configuration[:default_bucket] or "backup")
71
+ end
72
+
73
+ # Computes the MD5 for the uploaded file, in-case you
74
+ # wanna do corruption detection
75
+ def compute_md5(filename)
76
+ md5_digest = Digest::MD5.new
77
+ File.open(filename, 'r') do |file|
78
+ file.each_line do |line|
79
+ md5_digest << line
80
+ end
81
+ end
82
+ return md5_digest
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,15 @@
1
+ module Squidward
2
+ module Command
3
+ # Gem Versioning Helper and also displays current version to the user
4
+ class Version < Base
5
+ GEM_VERSION = "0.5"
6
+
7
+ # Displays current gem version to the user
8
+ def index(args = nil)
9
+ display("squidward-#{GEM_VERSION}")
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+
data/rakefile ADDED
@@ -0,0 +1,51 @@
1
+ require 'rubygems'
2
+ require 'lib/squidward'
3
+ require 'rake'
4
+ require 'spec'
5
+ require 'mocha'
6
+ require 'spec/rake/spectask'
7
+ require 'rake/gempackagetask'
8
+ require 'rake/rdoctask'
9
+
10
+ task :default => [:specs]
11
+
12
+ task :specs do
13
+ Spec::Rake::SpecTask.new('specs_run') do |t|
14
+ t.spec_files = FileList['spec/**/*.rb']
15
+ t.rcov = true
16
+ t.rcov_opts = ['--text-report', '--exclude', ".bundle,.gem,spec,Library,#{ENV['GEM_HOME']}", '--sort', 'coverage' ]
17
+ t.spec_opts = ['-cfn']
18
+ end
19
+ Rake::Task['specs_run'].invoke
20
+ end
21
+
22
+ namespace :dist do
23
+ spec = Gem::Specification.new do |s|
24
+ s.name = 'squidward'
25
+ s.version = Gem::Version.new(Squidward::Command::Version::GEM_VERSION)
26
+ s.summary = "Backup tool for taking SimpleDB domains to compressed files inside Amazon S3"
27
+ s.description = "A dead simple tool for doing backups of simpledb domains."
28
+ s.email = 'johnny.halife@me.com'
29
+ s.author = 'Johnny G. Halife'
30
+ s.homepage = 'http://squidward.heroku.com'
31
+
32
+ s.files = %w(rakefile) + Dir.glob("{bin,lib,spec}/**/*")
33
+ s.executables = "squidward"
34
+ s.default_executable = "squidward"
35
+
36
+ s.platform = Gem::Platform::RUBY
37
+ s.has_rdoc = true
38
+ s.rdoc_options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
39
+
40
+ s.require_path = "lib"
41
+ s.bindir = "bin"
42
+
43
+ # Dependencies
44
+ s.add_dependency 'aws-s3'
45
+ s.add_dependency 'sdbtools'
46
+ end
47
+
48
+ Rake::GemPackageTask.new(spec) do |pkg|
49
+ pkg.need_tar = true
50
+ end
51
+ end
@@ -0,0 +1,76 @@
1
+ # enabling the load of files from root (on RSpec)
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../')
3
+ require 'spec_config'
4
+
5
+ describe "base commands functionality" do
6
+
7
+ it "should prompt for AWS Credentials when they do not exist" do
8
+ Squidward::Command::Base.any_instance.expects(:read_credentials).returns(false)
9
+ Squidward::Command::Base.any_instance.expects(:ask_for_credentials).returns(["access_key", "secret_key"])
10
+ Squidward::Command::Base.any_instance.expects(:store_credentials).with(["access_key", "secret_key"])
11
+ my_command = Squidward::Command::Base.new()
12
+ end
13
+
14
+ it "should ask for credentials" do
15
+ Squidward::Command::Base.any_instance.expects(:read_credentials).returns(true)
16
+ my_command = Squidward::Command::Base.new()
17
+ my_command.expects(:puts).with("Enter your AWS credentials.")
18
+ my_command.expects(:print).with("Access Key ID: ")
19
+ my_command.expects(:print).with("Secret Access Key: ")
20
+ my_command.expects(:ask).returns("user")
21
+ my_command.expects(:ask_for_password).returns("password")
22
+ my_command.ask_for_credentials.should == ["user", "password"]
23
+ end
24
+
25
+ it "when asking for password should turn off echo and turn it on" do
26
+ Squidward::Command::Base.any_instance.expects(:read_credentials).returns(true)
27
+ my_command = Squidward::Command::Base.new()
28
+ my_command.expects(:echo_off)
29
+ my_command.expects(:echo_on)
30
+ my_command.expects(:puts)
31
+ my_command.expects(:ask).returns("password")
32
+ my_command.ask_for_password.should == "password"
33
+ end
34
+
35
+ it "should read the configuration when its prompted for config (no relaod)" do
36
+ Squidward::Command::Base.any_instance.expects(:read_credentials).returns(true)
37
+ configuration_path = File.expand_path(File.join(CONF_PATH, SETTINGS_FILE))
38
+ File.expects(:exists?).with(configuration_path).returns(true)
39
+ YAML::expects(:load_file).with(configuration_path)
40
+ my_command = Squidward::Command::Base.new()
41
+ my_command.configuration
42
+ end
43
+
44
+ it "should return the existing configuration when its prompted for config (no relaod)" do
45
+ Squidward::Command::Base.any_instance.expects(:read_credentials).returns(true)
46
+ configuration_path = File.expand_path(File.join(CONF_PATH, SETTINGS_FILE))
47
+ File.expects(:exists?).with(configuration_path).returns(true)
48
+ YAML::expects(:load_file).with(configuration_path).returns({:key => "value"})
49
+ my_command = Squidward::Command::Base.new()
50
+ my_command.configuration
51
+ # I'm doing it twice and it should work the second time (returns the cached copy)
52
+ my_command.configuration
53
+ end
54
+
55
+ it "should store configuration" do
56
+ Squidward::Command::Base.any_instance.expects(:read_credentials).returns(true)
57
+ File.expects(:open).with(File.expand_path(File.join(CONF_PATH, SETTINGS_FILE)), "w+")
58
+ FileUtils.expects(:mkpath).with(File.expand_path(CONF_PATH))
59
+ my_command = Squidward::Command::Base.new()
60
+ my_command.store_configuration({})
61
+ end
62
+
63
+ it "should read credentials when already there" do
64
+ Squidward::Command::Base.any_instance.expects(:configuration).returns({:credentials => {:secret_key => "secret", :access_key => "access"}}).twice
65
+ my_command = Squidward::Command::Base.new()
66
+ my_command.read_credentials.should == ["access", "secret"]
67
+ end
68
+
69
+ it "should store credentials after getting them" do
70
+ Squidward::Command::Base.any_instance.expects(:read_credentials).returns(false)
71
+ Squidward::Command::Base.any_instance.expects(:ask_for_credentials).returns(["access_key", "secret_key"])
72
+ Squidward::Command::Base.any_instance.expects(:configuration).returns({:credentials => {}})
73
+ Squidward::Command::Base.any_instance.expects(:store_configuration).returns()
74
+ my_command = Squidward::Command::Base.new()
75
+ end
76
+ end
@@ -0,0 +1,20 @@
1
+ # enabling the load of files from root (on RSpec)
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../')
3
+ require 'spec_config'
4
+
5
+ describe "runner command functionality" do
6
+ before do
7
+ Squidward::Command::Bucket.any_instance.expects(:read_credentials).returns(["access_key", "secret_key"])
8
+ end
9
+
10
+ it "should inherit the Base functionality" do
11
+ my_command = Squidward::Command::Bucket.new()
12
+ end
13
+
14
+ it "should set backup default bucket" do
15
+ my_command = Squidward::Command::Bucket.new()
16
+ my_command.expects(:configuration).returns({})
17
+ my_command.expects(:store_configuration).with({:default_bucket => "backup"})
18
+ my_command.set(["backup"])
19
+ end
20
+ end
@@ -0,0 +1,12 @@
1
+ # enabling the load of files from root (on RSpec)
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../')
3
+ require 'spec_config'
4
+
5
+ describe "runner command functionality" do
6
+ it "should clear the stored credentials" do
7
+ my_command = Squidward::Command::Credentials.new()
8
+ my_command.expects(:configuration).returns({:credentials => ["foo", "bar"]})
9
+ my_command.expects(:store_configuration).with({:credentials => nil})
10
+ my_command.clear
11
+ end
12
+ end
@@ -0,0 +1,28 @@
1
+ # enabling the load of files from root (on RSpec)
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../')
3
+ require 'spec_config'
4
+
5
+ describe "domain command functionality" do
6
+ it "should inherit the Base functionality" do
7
+ Squidward::Command::Domain.any_instance.expects(:read_credentials).returns(true)
8
+ my_command = Squidward::Command::Domain.new()
9
+ end
10
+
11
+ it "should store backup for domain" do
12
+ existing_domains = [{:domain => "domain", :vpath => "vpath"}]
13
+ Squidward::Command::Domain.any_instance.expects(:read_credentials).returns(true)
14
+ Squidward::Command::Domain.any_instance.expects(:configuration).returns({:domains => existing_domains})
15
+ Squidward::Command::Domain.any_instance.expects(:store_configuration).with({:domains => existing_domains + [{:domain => "domain_name", :vpath => "virtual_path"}]})
16
+ my_command = Squidward::Command::Domain.new()
17
+ my_command.backup(["domain_name", "virtual_path"])
18
+ end
19
+
20
+ it "should remove existing backup from the configuration and store new config" do
21
+ existing_domains = [{:domain => "domain", :vpath => "vpath"}]
22
+ Squidward::Command::Domain.any_instance.expects(:read_credentials).returns(true)
23
+ Squidward::Command::Domain.any_instance.expects(:configuration).returns({:domains => existing_domains})
24
+ Squidward::Command::Domain.any_instance.expects(:store_configuration).with({:domains => []})
25
+ my_command = Squidward::Command::Domain.new()
26
+ my_command.remove("domain")
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ # enabling the load of files from root (on RSpec)
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../')
3
+ require 'spec_config'
4
+
5
+ describe "runner command functionality" do
6
+ before do
7
+ Squidward::Command::Logs.any_instance.expects(:read_credentials).returns(["access_key", "secret_key"])
8
+ end
9
+
10
+ it "should inherit the Base functionality" do
11
+ my_command = Squidward::Command::Logs.new()
12
+ end
13
+
14
+ it "should set backup default bucket" do
15
+ my_command = Squidward::Command::Logs.new()
16
+ my_command.expects(:system).with("tail -n 25 #{File.expand_path(File.join(CONF_PATH, LOG_FILE))}")
17
+ my_command.index
18
+ end
19
+ end
@@ -0,0 +1,133 @@
1
+ # enabling the load of files from root (on RSpec)
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../')
3
+ require 'spec_config'
4
+
5
+ describe "runner command functionality" do
6
+ before do
7
+ Squidward::Command::Run.any_instance.expects(:read_credentials).returns(["access_key", "secret_key"])
8
+ AWS::S3::Base.expects(:establish_connection!).with(:access_key_id => "access_key", :secret_access_key => "secret_key")
9
+ end
10
+
11
+ it "should inherit the Base functionality" do
12
+ my_command = Squidward::Command::Run.new()
13
+ end
14
+
15
+ it "should return services to run from the configuration" do
16
+ configuration = {:domains => [{:domain => "domain1", :vpath => "path2"},
17
+ {:domain => "domain2", :vpath => "path2"}]}
18
+
19
+ my_command = Squidward::Command::Run.new()
20
+ my_command.expects(:configuration).returns(configuration)
21
+ my_command.domains.should == configuration[:domains]
22
+ end
23
+
24
+ it "should return services to run from the configuration" do
25
+ configuration = {:default_bucket => "backup"}
26
+ my_command = Squidward::Command::Run.new()
27
+ my_command.expects(:configuration).returns(configuration)
28
+ my_command.bucket.should == configuration[:default_bucket]
29
+ end
30
+
31
+ it "should return services to run from the configuration" do
32
+ my_command = Squidward::Command::Run.new()
33
+ my_command.expects(:configuration).returns({})
34
+ my_command.bucket.should == "backup"
35
+ end
36
+
37
+ it "should run the backup for each domain (and ensure the destination bucket exists)" do
38
+ my_command = Squidward::Command::Run.new()
39
+ my_command.expects(:ensure_bucket!).returns("backup.squidward.full").once
40
+ my_command.expects(:domains).returns([{:domain => "domain1", :vpath => "bucket1"}])
41
+ my_command.expects(:internal_run).with({:domain => "domain1", :vpath => "bucket1"})
42
+ my_command.index
43
+ end
44
+
45
+ it "should run the backup for each domain (and avoide creating the destination bucket when it exists)" do
46
+ my_command = Squidward::Command::Run.new()
47
+ my_command.expects(:bucket).returns("backup.squidward.full")
48
+ my_command.expects(:ensure_bucket!).with("backup.squidward.full")
49
+ my_command.expects(:domains).returns([{:domain => "domain1", :vpath => "bucket1"}])
50
+ my_command.expects(:internal_run).with({:domain => "domain1", :vpath => "bucket1"})
51
+ my_command.index
52
+ end
53
+
54
+ it "should dump a domain and return the file path to the dump result" do
55
+ FileUtils.expects(:mkpath).with(File.expand_path(File.join(CONF_PATH, TEMP_PATH))).returns("temp-path")
56
+ dump_file = File.expand_path(File.join(CONF_PATH, TEMP_PATH, "dump"))
57
+
58
+ my_command = Squidward::Command::Run.new()
59
+ my_command.expects(:generate_dump_filename).returns("dump")
60
+
61
+ (mock_selection = mock).expects(:results).returns([])
62
+ (mock_domain = mock).expects(:selection).returns(mock_selection)
63
+ (mock_db = mock).expects(:domain).with("custom-domain").returns(mock_domain)
64
+ my_command.expects(:database).returns(mock_db)
65
+
66
+ File.expects(:open).with(dump_file, "w+")
67
+ File.expects(:delete).with(dump_file)
68
+
69
+ my_command.expects(:`).with("tar -czf \"#{dump_file}.tar.gz\" -C \"#{File.dirname(dump_file)}\" \"#{File.basename(dump_file)}\"")
70
+ my_command.dump_domain("custom-domain").should == "#{dump_file}.tar.gz"
71
+ end
72
+
73
+ it "should generate the filename for the dumped file" do
74
+ now = Time.now
75
+ Time.expects(:now).returns(now).twice
76
+ my_command = Squidward::Command::Run.new()
77
+ my_command.generate_dump_filename("my-domain").should == "my-domain-#{Time.now.utc.strftime("%Y%m%d%H%M%S%Z%Y")}"
78
+ end
79
+
80
+ it "should do anything if domain does not exist" do
81
+ my_command = Squidward::Command::Run.new()
82
+ (mock_db = mock).expects(:domain_exists?).with("domain1").returns(false)
83
+ my_command.expects(:database).returns(mock_db)
84
+ my_command.internal_run(:domain => "domain1", :vpath => "/sdb/")
85
+ end
86
+
87
+ it "should do full blown backup of a single domain when it exists" do
88
+ my_command = Squidward::Command::Run.new()
89
+ my_command.expects(:bucket).returns("my_bucket")
90
+
91
+ my_command.expects(:generate_dump_filename).with("domain").returns("dump")
92
+
93
+ # the domain already exists
94
+ (mock_db = mock).expects(:domain_exists?).with("domain").returns(true)
95
+ my_command.expects(:database).returns(mock_db)
96
+
97
+ # ask the library to dump the files
98
+ my_command.expects(:dump_domain).with("domain").returns("mock/path/to/file.tar.gz")
99
+
100
+ # generate md5-base64 for the file
101
+ (mock_digest = mock).expects(:digest).returns("digest")
102
+ my_command.expects(:compute_md5).with("mock/path/to/file.tar.gz").returns(mock_digest)
103
+ Base64.expects(:encode64).with("digest").returns("base64digest")
104
+
105
+ # mock the file opening
106
+ my_command.expects(:open).with("mock/path/to/file.tar.gz").returns("contents")
107
+
108
+ AWS::S3::S3Object.expects(:store).with("/sdb/dump.tar.gz", "contents", "my_bucket", :content_md5 => "base64digest")
109
+
110
+ File.expects(:delete).with("mock/path/to/file.tar.gz")
111
+ my_command.internal_run(:domain => "domain", :vpath => "/sdb/")
112
+ end
113
+
114
+ it "should hold a singleton instance of the database" do
115
+ (mock_logger = mock).expects(:info)
116
+ Squidward::Command::Run.any_instance.expects(:logger).returns(mock_logger)
117
+ my_command = Squidward::Command::Run.new()
118
+ my_command.database.nil?.should == false
119
+ end
120
+
121
+ it "should look up for the bucket when calling ensure if it exists" do
122
+ my_command = Squidward::Command::Run.new()
123
+ AWS::S3::Bucket.expects(:find).with("my_bucket").returns(mock)
124
+ my_command.ensure_bucket!("my_bucket").nil?.should == false
125
+ end
126
+
127
+ it "should create the bucket when it does not exist" do
128
+ my_command = Squidward::Command::Run.new()
129
+ AWS::S3::Bucket.expects(:find).with("my_bucket").raises(StandardError)
130
+ AWS::S3::Bucket.expects(:create).with("my_bucket").returns(mock)
131
+ my_command.ensure_bucket!("my_bucket").nil?.should == false
132
+ end
133
+ end
@@ -0,0 +1,15 @@
1
+ # enabling the load of files from root (on RSpec)
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../')
3
+ require 'spec_config'
4
+
5
+ describe "runner command functionality" do
6
+ before do
7
+ Squidward::Command::Version.any_instance.expects(:read_credentials).returns(["access_key", "secret_key"])
8
+ end
9
+
10
+ it "should show version string as {gem_name}-{version}" do
11
+ my_command = Squidward::Command::Version.new()
12
+ my_command.expects(:display).with("squidward-#{Squidward::Command::Version::GEM_VERSION}")
13
+ my_command.index
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ class Squidward::Command::Mock
2
+ def index
3
+ end
4
+
5
+ def mock_method
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec'
2
+ require 'mocha'
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../')
5
+ require 'lib/squidward'
6
+
7
+ require 'mock_command'
8
+
9
+ Spec::Runner.configure do |config|
10
+ config.mock_with :mocha
11
+ end
@@ -0,0 +1,28 @@
1
+ # enabling the load of files from root (on RSpec)
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../')
3
+ require 'spec_config'
4
+
5
+ describe "launcher behavior" do
6
+ it "should invoke the proper command and method" do
7
+ (command = mock).expects(:run)
8
+ Squidward::Command::Mock.expects(:new).returns(command)
9
+ Squidward::Command.run!("mock:run", nil)
10
+ end
11
+
12
+ it "should invoke index method as the default method when not provided" do
13
+ (command = mock).expects(:index)
14
+ Squidward::Command::Mock.expects(:new).returns(command)
15
+ Squidward::Command.run!("mock", nil)
16
+ end
17
+
18
+ it "should raise missing command when the method is invalid" do
19
+ Squidward::Command.expects(:puts).with("Unknown command. Run 'squidward help' for usage information.")
20
+ Squidward::Command.run!("invalid", nil)
21
+ end
22
+
23
+ it "should raise missing command when the command action does not exist" do
24
+ Squidward::Command.expects(:puts).with("Unknown command. Run 'squidward help' for usage information.")
25
+ Squidward::Command::Mock.expects(:new).returns(mock)
26
+ Squidward::Command.run!("mock:non-existing", nil)
27
+ end
28
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: squidward
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 5
8
+ version: "0.5"
9
+ platform: ruby
10
+ authors:
11
+ - Johnny G. Halife
12
+ autorequire:
13
+ bindir: bin
14
+ cert_chain: []
15
+
16
+ date: 2010-04-26 00:00:00 -03:00
17
+ default_executable: squidward
18
+ dependencies:
19
+ - !ruby/object:Gem::Dependency
20
+ name: aws-s3
21
+ prerelease: false
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ segments:
27
+ - 0
28
+ version: "0"
29
+ type: :runtime
30
+ version_requirements: *id001
31
+ - !ruby/object:Gem::Dependency
32
+ name: sdbtools
33
+ prerelease: false
34
+ requirement: &id002 !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ segments:
39
+ - 0
40
+ version: "0"
41
+ type: :runtime
42
+ version_requirements: *id002
43
+ description: A dead simple tool for doing backups of simpledb domains.
44
+ email: johnny.halife@me.com
45
+ executables:
46
+ - squidward
47
+ extensions: []
48
+
49
+ extra_rdoc_files: []
50
+
51
+ files:
52
+ - rakefile
53
+ - bin/squidward
54
+ - lib/squidward/command.rb
55
+ - lib/squidward/commands/base.rb
56
+ - lib/squidward/commands/bucket.rb
57
+ - lib/squidward/commands/credentials.rb
58
+ - lib/squidward/commands/domain.rb
59
+ - lib/squidward/commands/help.rb
60
+ - lib/squidward/commands/info.rb
61
+ - lib/squidward/commands/logs.rb
62
+ - lib/squidward/commands/run.rb
63
+ - lib/squidward/commands/version.rb
64
+ - lib/squidward.rb
65
+ - spec/commands/base_test.rb
66
+ - spec/commands/bucket_test.rb
67
+ - spec/commands/credentials_test.rb
68
+ - spec/commands/domain_test.rb
69
+ - spec/commands/logs_test.rb
70
+ - spec/commands/run_test.rb
71
+ - spec/commands/version_test.rb
72
+ - spec/mock_command.rb
73
+ - spec/spec_config.rb
74
+ - spec/squidward_test.rb
75
+ has_rdoc: true
76
+ homepage: http://squidward.heroku.com
77
+ licenses: []
78
+
79
+ post_install_message:
80
+ rdoc_options:
81
+ - --line-numbers
82
+ - --inline-source
83
+ - -A cattr_accessor=object
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ segments:
91
+ - 0
92
+ version: "0"
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ segments:
98
+ - 0
99
+ version: "0"
100
+ requirements: []
101
+
102
+ rubyforge_project:
103
+ rubygems_version: 1.3.6
104
+ signing_key:
105
+ specification_version: 3
106
+ summary: Backup tool for taking SimpleDB domains to compressed files inside Amazon S3
107
+ test_files: []
108
+