appshot 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +21 -0
- data/Gemfile +19 -0
- data/Guardfile +19 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/README.rdoc +20 -0
- data/Rakefile +44 -0
- data/appshot.conf.example +7 -0
- data/appshot.gemspec +32 -0
- data/bin/appshot +32 -0
- data/features/appshot.feature +55 -0
- data/features/step_definitions/appshot_steps.rb +3 -0
- data/features/support/env.rb +18 -0
- data/lib/appshot/app/mysql.rb +43 -0
- data/lib/appshot/app.rb +1 -0
- data/lib/appshot/filesystem/dm.rb +53 -0
- data/lib/appshot/filesystem.rb +1 -0
- data/lib/appshot/version.rb +3 -0
- data/lib/appshot/volume/ebs_prune.rb +38 -0
- data/lib/appshot/volume/ebs_snapshot.rb +34 -0
- data/lib/appshot/volume/ebs_volume.rb +65 -0
- data/lib/appshot/volume.rb +7 -0
- data/lib/appshot.rb +93 -0
- data/rvmrc.example +52 -0
- data/spec/lib/appshot/app/mysql_spec.rb +37 -0
- data/spec/lib/appshot/filesystem/dm_spec.rb +39 -0
- data/spec/lib/appshot/volume/ebs_prune_spec.rb +39 -0
- data/spec/lib/appshot/volume/ebs_snapshot_spec.rb +43 -0
- data/spec/lib/appshot/volume/ebs_volume_spec.rb +125 -0
- data/spec/lib/appshot_spec.rb +64 -0
- data/spec/spec_helper.rb +7 -0
- metadata +304 -0
data/lib/appshot.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'appshot/version'
|
2
|
+
require 'appshot/volume'
|
3
|
+
require 'appshot/app'
|
4
|
+
require 'appshot/filesystem'
|
5
|
+
require 'awesome_print'
|
6
|
+
|
7
|
+
class Appshot
|
8
|
+
def initialize(config)
|
9
|
+
@appshots = {}
|
10
|
+
@callables = []
|
11
|
+
instance_eval(config)
|
12
|
+
end
|
13
|
+
|
14
|
+
def empty?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def appshot_count
|
19
|
+
@appshots.size
|
20
|
+
end
|
21
|
+
|
22
|
+
def appshot_names
|
23
|
+
@appshots ? @appshots.keys : []
|
24
|
+
end
|
25
|
+
|
26
|
+
##############
|
27
|
+
# DSL Keywords
|
28
|
+
##############
|
29
|
+
def appshot(appshot_name, &block)
|
30
|
+
if block_given?
|
31
|
+
@callables = []
|
32
|
+
@appshots[appshot_name.to_s] = instance_eval(&block)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def comment(arg)
|
37
|
+
@callables
|
38
|
+
end
|
39
|
+
|
40
|
+
def xfs(args={})
|
41
|
+
@callables << Appshot::DM.new(args)
|
42
|
+
end
|
43
|
+
|
44
|
+
def ext4(args={})
|
45
|
+
@callables << Appshot::DM.new(args)
|
46
|
+
end
|
47
|
+
|
48
|
+
def ebs_snapshot(args={})
|
49
|
+
@callables << Appshot::EBS_Snapshot.new(args)
|
50
|
+
end
|
51
|
+
|
52
|
+
def mysql(args={})
|
53
|
+
@callables << Appshot::Mysql.new(args)
|
54
|
+
end
|
55
|
+
|
56
|
+
def ebs_prune(args={})
|
57
|
+
@callables << Appshot::EBS_Prune.new(args)
|
58
|
+
end
|
59
|
+
|
60
|
+
###############
|
61
|
+
# Internals
|
62
|
+
###############
|
63
|
+
|
64
|
+
def appshots
|
65
|
+
@appshots
|
66
|
+
end
|
67
|
+
|
68
|
+
def execute_callables
|
69
|
+
@appshots.each do |appshot, callable|
|
70
|
+
first_call = callable.shift
|
71
|
+
first_call.call(callable) unless first_call.nil?
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def run_pass(options, args)
|
76
|
+
if options["list-appshots"]
|
77
|
+
puts list_appshots if options["list-appshots"]
|
78
|
+
else
|
79
|
+
execute_callables unless options["trial-run"]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def list_appshots
|
84
|
+
case @appshots.count
|
85
|
+
when 0
|
86
|
+
"There are no appshots configured"
|
87
|
+
when 1
|
88
|
+
"There is one appshot configured: #{@appshots.keys.first.to_s}"
|
89
|
+
else
|
90
|
+
"There are #{@appshots.count} appshots configured: #{@appshots.keys.join(', ')}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/rvmrc.example
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# This is an RVM Project .rvmrc file, used to automatically load the ruby
|
4
|
+
# development environment upon cd'ing into the directory
|
5
|
+
|
6
|
+
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional,
|
7
|
+
# Only full ruby name is supported here, for short names use:
|
8
|
+
# echo "rvm use 1.9.3" > .rvmrc
|
9
|
+
environment_id="ruby-1.9.3"
|
10
|
+
|
11
|
+
# Uncomment the following lines if you want to verify rvm version per project
|
12
|
+
# rvmrc_rvm_version="1.10.2" # 1.10.1 seams as a safe start
|
13
|
+
# eval "$(echo ${rvm_version}.${rvmrc_rvm_version} | awk -F. '{print "[[ "$1*65536+$2*256+$3" -ge "$4*65536+$5*256+$6" ]]"}' )" || {
|
14
|
+
# echo "This .rvmrc file requires at least RVM ${rvmrc_rvm_version}, aborting loading."
|
15
|
+
# return 1
|
16
|
+
# }
|
17
|
+
|
18
|
+
# First we attempt to load the desired environment directly from the environment
|
19
|
+
# file. This is very fast and efficient compared to running through the entire
|
20
|
+
# CLI and selector. If you want feedback on which environment was used then
|
21
|
+
# insert the word 'use' after --create as this triggers verbose mode.
|
22
|
+
if [[ -d "${rvm_path:-$HOME/.rvm}/environments"
|
23
|
+
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
|
24
|
+
then
|
25
|
+
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
|
26
|
+
[[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]] &&
|
27
|
+
\. "${rvm_path:-$HOME/.rvm}/hooks/after_use" || true
|
28
|
+
if [[ $- == *i* ]] # check for interactive shells
|
29
|
+
then echo "Using: $(tput setaf 2)$GEM_HOME$(tput sgr0)" # show the user the ruby and gemset they are using in green
|
30
|
+
else echo "Using: $GEM_HOME" # don't use colors in non-interactive shells
|
31
|
+
fi
|
32
|
+
else
|
33
|
+
# If the environment file has not yet been created, use the RVM CLI to select.
|
34
|
+
rvm --create use "$environment_id" || {
|
35
|
+
echo "Failed to create RVM environment '${environment_id}'."
|
36
|
+
return 1
|
37
|
+
}
|
38
|
+
fi
|
39
|
+
|
40
|
+
# If you use bundler, this might be useful to you:
|
41
|
+
# if [[ -s Gemfile ]] && {
|
42
|
+
# ! builtin command -v bundle >/dev/null ||
|
43
|
+
# builtin command -v bundle | grep $rvm_path/bin/bundle >/dev/null
|
44
|
+
# }
|
45
|
+
# then
|
46
|
+
# printf "%b" "The rubygem 'bundler' is not installed. Installing it now.\n"
|
47
|
+
# gem install bundler
|
48
|
+
# fi
|
49
|
+
# if [[ -s Gemfile ]] && builtin command -v bundle >/dev/null
|
50
|
+
# then
|
51
|
+
# bundle install | grep -vE '^Using|Your bundle is complete'
|
52
|
+
# fi
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'appshot/app/mysql'
|
2
|
+
require 'awesome_print'
|
3
|
+
|
4
|
+
describe Appshot::Mysql do
|
5
|
+
subject { Appshot::Mysql.new }
|
6
|
+
it { should be_a_kind_of Appshot::Mysql }
|
7
|
+
it { subject.respond_to?(:call).should be_true }
|
8
|
+
|
9
|
+
describe "#call" do
|
10
|
+
before do
|
11
|
+
Mysql2::Client.stub(:new).and_return(mysql)
|
12
|
+
end
|
13
|
+
let(:mysql) { double(:mysql2, query: true, close: true) }
|
14
|
+
|
15
|
+
it "should require an argument to #call" do
|
16
|
+
lambda { subject.call }.should raise_error(ArgumentError)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should invoke #call on the next object in the call_chain" do
|
20
|
+
items = [double(:appshot1), double(:appshot2)]
|
21
|
+
items.first.should_receive(:call).with(items).and_return(true)
|
22
|
+
subject.call(items)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should run #lock before call is invoked" do
|
26
|
+
items = [Appshot::Mysql.new, Appshot::Mysql.new]
|
27
|
+
items.first.should_receive(:lock)
|
28
|
+
subject.call(items)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should run #unlock after call is invoked" do
|
32
|
+
items = [Appshot::Mysql.new, Appshot::Mysql.new]
|
33
|
+
items.first.should_receive(:unlock)
|
34
|
+
subject.call(items)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'appshot/filesystem/dm'
|
2
|
+
|
3
|
+
describe Appshot::DM do
|
4
|
+
subject {
|
5
|
+
Appshot::DM.any_instance.stub(validate_mount_point: true)
|
6
|
+
Appshot::DM.new(mount_point: "/")
|
7
|
+
}
|
8
|
+
|
9
|
+
it { should be_a_kind_of Appshot::DM }
|
10
|
+
it { subject.respond_to?(:call).should be_true }
|
11
|
+
|
12
|
+
describe "#call" do
|
13
|
+
it "should invoke #call on the next object in the call_chain" do
|
14
|
+
Appshot::DM.any_instance.stub(:freeze).and_return(true)
|
15
|
+
Appshot::DM.any_instance.stub(:unfreeze).and_return(true)
|
16
|
+
items = [double(:appshot1), double(:appshot2)]
|
17
|
+
items.first.should_receive(:call).with(items).and_return(true)
|
18
|
+
subject.call(items)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should run #freeze and #unfreeze around a call being invoked" do
|
22
|
+
Appshot::DM.any_instance.stub(validate_mount_point: true)
|
23
|
+
first_dm = Appshot::DM.new(mount_point: "/tmp/fake_mount")
|
24
|
+
first_dm.stub(:freeze).and_return(true)
|
25
|
+
first_dm.stub(:unfreeze).and_return(true)
|
26
|
+
items = [double(:appshot_dm)]
|
27
|
+
items.first.should_receive(:call).with([]).and_return(true)
|
28
|
+
first_dm.call(items)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should not allow root paths" do
|
32
|
+
lambda{ Appshot::DM.new(mount_point: "/") }.should raise_error "We cannot currently unfreeze the root filesystem, leaving your system unusable. Aborting."
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should verify that the given mount point exists and is actually a mount point" do
|
36
|
+
lambda{ Appshot::DM.new(mount_point: "/tmp/fake_mount_for_appshot_testing") }.should raise_error "Your mount point: '/tmp/fake_mount_for_appshot_testing' is not a mount point!"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "appshot/volume/ebs_prune"
|
2
|
+
|
3
|
+
describe Appshot::EBS_Prune do
|
4
|
+
subject { Appshot::EBS_Prune.new }
|
5
|
+
|
6
|
+
it { should be_a_kind_of Appshot::EBS_Prune }
|
7
|
+
it { subject.respond_to?(:call).should be_true }
|
8
|
+
|
9
|
+
it "should invoke #call on the next object in the call_chain" do
|
10
|
+
next_item = [double(:appshot)]
|
11
|
+
subject.should_receive(:call).with(next_item).and_return(true)
|
12
|
+
subject.call(next_item)
|
13
|
+
end
|
14
|
+
describe "validating arguments" do
|
15
|
+
let(:opts) { { volume_id: "vol-eba11eee", snapshots_to_keep: 10, minimum_retention_days: 5, aws_access_key_id: "BOO", aws_secret_access_key: "YAH" } }
|
16
|
+
subject { Appshot::EBS_Prune.new(final_opts) }
|
17
|
+
|
18
|
+
context "with no volume_id" do
|
19
|
+
let(:final_opts) { opts.delete_if { |k,v| k == :volume_id } }
|
20
|
+
it "requires a volume_id argument" do
|
21
|
+
lambda { subject.valid? }.should raise_error ArgumentError, "volume_id must be specified for an ebs_prune"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "with no aws_access_key_id" do
|
26
|
+
let(:final_opts) { opts.delete_if { |k,v| k == :aws_access_key_id } }
|
27
|
+
it "requires a aws_access_key_id argument" do
|
28
|
+
lambda { subject.valid? }.should raise_error ArgumentError, "aws_access_key_id must be specified for an ebs_prune"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "with no aws_secret_access_key" do
|
33
|
+
let(:final_opts) { opts.delete_if { |k,v| k == :aws_secret_access_key } }
|
34
|
+
it "requires a aws_secret_access_key argument" do
|
35
|
+
lambda { subject.valid? }.should raise_error ArgumentError, "aws_secret_access_key must be specified for an ebs_prune"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'appshot/volume/ebs_snapshot'
|
2
|
+
|
3
|
+
describe Appshot::EBS_Snapshot do
|
4
|
+
|
5
|
+
subject { Appshot::EBS_Snapshot.new }
|
6
|
+
|
7
|
+
it { should be_a_kind_of Appshot::EBS_Snapshot }
|
8
|
+
it { subject.respond_to?(:call).should be_true }
|
9
|
+
|
10
|
+
it "should invoke #call on the next object in the call_chain" do
|
11
|
+
next_item = [double(:appshot)]
|
12
|
+
subject.should_receive(:call).with(next_item).and_return(true)
|
13
|
+
subject.call(next_item)
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "validating arguments" do
|
17
|
+
let(:opts) { { volume_id: "vol-12341234", aws_access_key_id: "BOO", aws_secret_access_key: "YAH" } }
|
18
|
+
subject { Appshot::EBS_Snapshot.new(final_opts) }
|
19
|
+
|
20
|
+
context "with no volume_id" do
|
21
|
+
let(:final_opts) { opts.delete_if { |k,v| k == :volume_id } }
|
22
|
+
it "requires a volume_id argument" do
|
23
|
+
lambda { subject.valid? }.should raise_error ArgumentError, "volume_id must be specified for an ebs_snapshot"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "with no aws_access_key_id" do
|
28
|
+
let(:final_opts) { opts.delete_if { |k,v| k == :aws_access_key_id } }
|
29
|
+
it "requires an aws_access_key_id argument" do
|
30
|
+
lambda { subject.valid? }.should raise_error ArgumentError, "aws_access_key_id must be specified for an ebs_snapshot"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "with no aws_secret_access_key" do
|
35
|
+
let(:final_opts) { opts.delete_if { |k,v| k == :aws_secret_access_key } }
|
36
|
+
it "requires an aws_secret_access_key argument" do
|
37
|
+
lambda { subject.valid? }.should raise_error ArgumentError, "aws_secret_access_key must be specified for an ebs_snapshot"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "appshot"
|
3
|
+
require "appshot/volume"
|
4
|
+
require "appshot/volume/ebs_volume"
|
5
|
+
require "timecop"
|
6
|
+
|
7
|
+
describe Appshot::EBS_Volume do
|
8
|
+
before do
|
9
|
+
Fog::Compute::AWS::Mock.reset
|
10
|
+
Fog.mock!
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:ebs) { described_class.new(:provider => 'AWS',
|
14
|
+
:region => region,
|
15
|
+
:aws_access_key_id => aws_access_key_id,
|
16
|
+
:aws_secret_access_key => aws_secret_access_key,
|
17
|
+
) }
|
18
|
+
let(:fog_compute) { ebs.instance_variable_get(:@fog) }
|
19
|
+
let(:region) { "us-east-1" }
|
20
|
+
let(:aws_access_key_id) { "access_key_shhh" }
|
21
|
+
let(:aws_secret_access_key) { "secret_key_shhh" }
|
22
|
+
|
23
|
+
describe "basic setup" do
|
24
|
+
it "should be a class" do
|
25
|
+
described_class.should be_a_kind_of Class
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should have a volume_id" do
|
29
|
+
ebs.respond_to?(:volume_id).should be_true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "given a volume_id" do
|
34
|
+
let(:snapshot_count) { 2 }
|
35
|
+
let(:volume_id) { volumes.first.id }
|
36
|
+
|
37
|
+
let(:volumes) do
|
38
|
+
with_no_mock_delay do
|
39
|
+
[ create_volume("us-east-1a"),
|
40
|
+
create_volume("us-east-1b"), ]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
let(:snaps) do
|
45
|
+
with_no_mock_delay do
|
46
|
+
snapshot_count.times do |i|
|
47
|
+
ebs.snap(volume_id, "This is test snapshot ##{i}")
|
48
|
+
end
|
49
|
+
ebs.snapshots_for(volume_id)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should create a snap id with a valid format" do
|
54
|
+
snaps.first.id.should =~ /^snap-*/
|
55
|
+
end
|
56
|
+
|
57
|
+
it "must finish a snapshot of the volume" do
|
58
|
+
snaps.first.state.should == "completed"
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should know how many snapshots there are for a given volume" do
|
62
|
+
snaps
|
63
|
+
ebs.snapshots_for(volumes.first.id).count.should == 2
|
64
|
+
ebs.snapshots_for(volumes.last.id).count.should == 0
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "pruning snapshots" do
|
68
|
+
context "with a minimum age" do
|
69
|
+
let(:snapshot_count) { 5 }
|
70
|
+
|
71
|
+
it "should not prune snapshots younger than the minimum age" do
|
72
|
+
with_no_mock_delay do
|
73
|
+
Timecop.freeze(Time.now - (3 * 86400))
|
74
|
+
ebs.snap(volume_id, "This is test snapshot #2")
|
75
|
+
Timecop.freeze(Time.now + 86400)
|
76
|
+
ebs.snap(volume_id, "This is test snapshot #3")
|
77
|
+
Timecop.freeze(Time.now + 86400)
|
78
|
+
ebs.snap(volume_id, "This is test snapshot #4")
|
79
|
+
Timecop.return
|
80
|
+
end
|
81
|
+
ebs.snapshots_for(volumes.first.id).count.should == 3
|
82
|
+
ebs.prune_snapshots(volumes.first.id, snapshots_to_keep: 1, not_after_time: Time.now - (3 * 86400))
|
83
|
+
ebs.snapshots_for(volumes.first.id).count.should == 2
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "without a minimum age" do
|
88
|
+
let(:snapshot_count) { 3 }
|
89
|
+
|
90
|
+
it "should prune snapshots for a given volume to a snapshot count" do
|
91
|
+
snaps
|
92
|
+
ebs.snapshots_for(volumes.first.id).count.should == 3
|
93
|
+
ebs.prune_snapshots(volumes.first.id, snapshots_to_keep: 1)
|
94
|
+
ebs.snapshots_for(volumes.first.id).count.should == 1
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def create_server(zone = "us-east-1" + %w(a b c d).sample)
|
104
|
+
with_no_mock_delay do
|
105
|
+
new_server = fog_compute.servers.create
|
106
|
+
new_server.reload.id
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def create_volume(zone = "us-east-1" + %w(a b c d).sample, size = 20)
|
111
|
+
with_no_mock_delay do
|
112
|
+
new_volume = fog_compute.volumes.create(:availability_zone => zone, :size => size)
|
113
|
+
new_volume.reload
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def with_no_mock_delay
|
118
|
+
delay = Fog::Mock.delay
|
119
|
+
Fog::Mock.delay = 0
|
120
|
+
return_object = yield
|
121
|
+
Fog::Mock.delay = delay
|
122
|
+
return_object
|
123
|
+
end
|
124
|
+
|
125
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require "appshot"
|
2
|
+
require 'awesome_print'
|
3
|
+
|
4
|
+
describe Appshot do
|
5
|
+
|
6
|
+
let(:app) { Appshot.new("") }
|
7
|
+
|
8
|
+
it "is a class" do
|
9
|
+
described_class.should be_a_kind_of Class
|
10
|
+
end
|
11
|
+
|
12
|
+
it "has a method #appshot" do
|
13
|
+
app.respond_to?(:appshot).should be_true
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#appshot" do
|
17
|
+
it "should not add an appshot with an empty block" do
|
18
|
+
app.appshot_count.should == 0
|
19
|
+
app.appshot("my_account")
|
20
|
+
app.appshot_count.should == 0
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should add an appshot to the store" do
|
24
|
+
app.appshot_count.should == 0
|
25
|
+
app.appshot("my_account") {}
|
26
|
+
app.appshot_count.should == 1
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should add an appshot with the right name" do
|
30
|
+
app.appshot("my_account") {}
|
31
|
+
app.appshot_names.include?("my_account").should be_true
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should accept a block" do
|
35
|
+
app.appshot("my_account") do
|
36
|
+
comment "my_account snapshot run"
|
37
|
+
end
|
38
|
+
app.appshots["my_account"].should_not be_nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#list_appshots" do
|
43
|
+
context "with no appshots in config file" do
|
44
|
+
it "should give a 'no appshots' message" do
|
45
|
+
app.list_appshots.should == "There are no appshots configured"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
context "with one appshot in config file" do
|
49
|
+
it "should give a 'one appshot' message" do
|
50
|
+
app.appshot("one") {}
|
51
|
+
app.list_appshots.should == "There is one appshot configured: one"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "running an appshot" do
|
57
|
+
it "should run appshots" do
|
58
|
+
app.appshot("my_account") do
|
59
|
+
comment "my account snapshot execute"
|
60
|
+
end
|
61
|
+
app.execute_callables
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|