appshot 0.0.1
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/.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
|