squidward 0.5

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/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
+