shelly 0.0.33 → 0.0.34

Sign up to get free protection for your applications and to get access to all the features.
data/lib/shelly/app.rb CHANGED
@@ -8,6 +8,8 @@ module Shelly
8
8
  attr_accessor :code_name, :databases, :ruby_version, :environment,
9
9
  :git_url, :domains
10
10
 
11
+ autoload :Backup, "shelly/backup"
12
+
11
13
  def initialize(code_name = nil)
12
14
  self.code_name = code_name
13
15
  end
@@ -59,15 +61,22 @@ module Shelly
59
61
  end
60
62
 
61
63
  def application_logs
62
- shelly.application_logs(self.code_name)
64
+ shelly.application_logs(code_name)
63
65
  end
64
66
 
65
67
  def database_backups
66
- shelly.database_backups(code_name)
68
+ shelly.database_backups(code_name).map do |attributes|
69
+ Shelly::Backup.new(attributes.merge("code_name" => code_name))
70
+ end
71
+ end
72
+
73
+ def database_backup(handler)
74
+ attributes = shelly.database_backup(code_name, handler)
75
+ Shelly::Backup.new(attributes.merge("code_name" => code_name))
67
76
  end
68
77
 
69
78
  def logs
70
- shelly.cloud_logs(self.code_name)
79
+ shelly.cloud_logs(code_name)
71
80
  end
72
81
 
73
82
  def start
@@ -132,4 +141,3 @@ module Shelly
132
141
  end
133
142
  end
134
143
  end
135
-
@@ -0,0 +1,16 @@
1
+ module Shelly
2
+ class Backup < Model
3
+ attr_reader :filename, :size, :human_size, :code_name
4
+
5
+ def initialize(attributes = {})
6
+ @filename = attributes["filename"]
7
+ @size = attributes["size"]
8
+ @human_size = attributes["human_size"]
9
+ @code_name = attributes["code_name"]
10
+ end
11
+
12
+ def download(callback)
13
+ shelly.download_backup(code_name, filename, callback)
14
+ end
15
+ end
16
+ end
@@ -1,4 +1,6 @@
1
1
  require "shelly/cli/command"
2
+ require "shelly/backup"
3
+ require "shelly/download_progress_bar"
2
4
 
3
5
  module Shelly
4
6
  module CLI
@@ -15,13 +17,15 @@ module Shelly
15
17
  say_error "No Cloudfile found" unless Cloudfile.present?
16
18
  multiple_clouds(options[:cloud], "backup list", "Select cloud to view database backups for using:")
17
19
  backups = @app.database_backups
18
- unless backups.empty?
19
- backups.unshift({"filename" => "Filename", "size" => "Size"})
20
+ if backups.present?
21
+ to_display = [["Filename", "| Size"]]
22
+ backups.each do |backup|
23
+ to_display << [backup.filename, "| #{backup.human_size}"]
24
+ end
25
+
20
26
  say "Available backups:", :green
21
27
  say_new_line
22
- print_table(backups.map do |backup|
23
- [backup['filename'], "| #{backup['size']}"]
24
- end, :ident => 2)
28
+ print_table(to_display, :ident => 2)
25
29
  else
26
30
  say "No database backups available"
27
31
  end
@@ -32,6 +36,26 @@ module Shelly
32
36
  say_error e.message
33
37
  end
34
38
  end
39
+
40
+ method_option :cloud, :type => :string, :aliases => "-c", :desc => "Specify which cloud list backups for"
41
+ desc "get [FILENAME]", "Downloads specified or last backup to current directory"
42
+ def get(handler = "last")
43
+ multiple_clouds(options[:cloud], "backup get [FILENAME]", "Select cloud for which you want download backup")
44
+
45
+ backup = @app.database_backup(handler)
46
+ bar = Shelly::DownloadProgressBar.new(backup.size)
47
+ backup.download(bar.progress_callback)
48
+
49
+ say_new_line
50
+ say "Backup file saved to #{backup.filename}", :green
51
+ rescue Client::APIError => e
52
+ if e.not_found?
53
+ say_error "Backup not found", :with_exit => false
54
+ say "You can list available backups with 'shelly backup list' command"
55
+ else
56
+ raise e
57
+ end
58
+ end
35
59
  end
36
60
  end
37
61
  end
data/lib/shelly/client.rb CHANGED
@@ -4,8 +4,11 @@ require "json"
4
4
  module Shelly
5
5
  class Client
6
6
  class APIError < Exception
7
- def initialize(response_body)
7
+ attr_reader :status_code
8
+
9
+ def initialize(response_body, status_code)
8
10
  @response = JSON.parse(response_body)
11
+ @status_code = status_code
9
12
  end
10
13
 
11
14
  def message
@@ -23,10 +26,14 @@ module Shelly
23
26
  def validation?
24
27
  message == "Validation Failed"
25
28
  end
29
+
30
+ def not_found?
31
+ status_code == 404
32
+ end
26
33
 
27
34
  def unauthorized?
28
- # FIXME: Return unauthorized if user has no access to cloud
29
- # Return 404 if cloud doesn't exist, should be fixed in API
35
+ # FIXME: Return unauthorized if user has no access to cloud, checked by 401 status code
36
+ # Return 404 if child of app doesn't exist, should be fixed in API
30
37
  message == "Unauthorized" || message =~ /Cloud .+ not found/
31
38
  end
32
39
 
@@ -37,6 +44,8 @@ module Shelly
37
44
  end
38
45
  end
39
46
 
47
+ attr_reader :email, :password
48
+
40
49
  def initialize(email = nil, password = nil)
41
50
  @email = email
42
51
  @password = password
@@ -121,6 +130,10 @@ module Shelly
121
130
  def database_backups(code_name)
122
131
  get("/apps/#{code_name}/database_backups")
123
132
  end
133
+
134
+ def database_backup(code_name, handler)
135
+ get("/apps/#{code_name}/database_backups/#{handler}")
136
+ end
124
137
 
125
138
  def ssh_key_available?(ssh_key)
126
139
  get("/users/new", :ssh_key => ssh_key)
@@ -149,6 +162,23 @@ module Shelly
149
162
  def delete(path, params = {})
150
163
  request(path, :delete, params)
151
164
  end
165
+
166
+ def download_backup(cloud, filename, progress_callback = nil)
167
+ File.open(filename, "w") do |out|
168
+ process_response = lambda do |response|
169
+ response.read_body do |chunk|
170
+ out.write(chunk)
171
+ progress_callback.call(chunk.size) if progress_callback
172
+ end
173
+ end
174
+
175
+ options = request_parameters("/apps/#{cloud}/database_backups/#{filename}", :get)
176
+ options = options.merge(:block_response => process_response,
177
+ :headers => {:accept => "application/x-gzip"})
178
+
179
+ RestClient::Request.execute(options)
180
+ end
181
+ end
152
182
 
153
183
  def request(path, method, params = {})
154
184
  options = request_parameters(path, method, params)
@@ -177,7 +207,7 @@ module Shelly
177
207
 
178
208
  def process_response(response)
179
209
  if [401, 404, 422, 500].include?(response.code)
180
- raise APIError.new(response.body)
210
+ raise APIError.new(response.body, response.code)
181
211
  end
182
212
 
183
213
  response.return!
@@ -0,0 +1,14 @@
1
+ require "progressbar"
2
+
3
+ module Shelly
4
+ class DownloadProgressBar < ProgressBar
5
+ def initialize(total)
6
+ super("Progress", total)
7
+ self.format_arguments = [:title, :percentage, :bar, :stat_for_file_transfer]
8
+ end
9
+
10
+ def progress_callback
11
+ lambda { |size| inc(size) }
12
+ end
13
+ end
14
+ end
@@ -1,3 +1,3 @@
1
1
  module Shelly
2
- VERSION = "0.0.33"
2
+ VERSION = "0.0.34"
3
3
  end
data/shelly.gemspec CHANGED
@@ -26,6 +26,7 @@ Gem::Specification.new do |s|
26
26
  s.add_runtime_dependency "rest-client"
27
27
  s.add_runtime_dependency "json"
28
28
  s.add_runtime_dependency "wijet-launchy"
29
+ s.add_runtime_dependency "progressbar"
29
30
 
30
31
  s.files = `git ls-files`.split("\n")
31
32
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -167,6 +167,30 @@ config
167
167
  end
168
168
  end
169
169
 
170
+ describe "#database_backup" do
171
+ before do
172
+ @description = {
173
+ "filename" => "backup.tar.gz",
174
+ "size" => 1234,
175
+ "human_size" => "2KB"
176
+ }
177
+ @client.stub(:database_backup).and_return(@description)
178
+ end
179
+
180
+ it "should fetch backup from API" do
181
+ @client.should_receive(:database_backup).with("foo-staging", "backup.tar.gz")
182
+ @app.database_backup("backup.tar.gz")
183
+ end
184
+
185
+ it "should initialize backup object" do
186
+ backup = @app.database_backup("backup.tar.gz")
187
+ backup.code_name.should == "foo-staging"
188
+ backup.size.should == 1234
189
+ backup.human_size.should == "2KB"
190
+ backup.filename.should == "backup.tar.gz"
191
+ end
192
+ end
193
+
170
194
  describe "#create" do
171
195
  context "without providing domain" do
172
196
  it "should create the app on shelly cloud via API client" do
@@ -0,0 +1,34 @@
1
+ require "spec_helper"
2
+ require "shelly/backup"
3
+
4
+ describe Shelly::Backup do
5
+ before do
6
+ @client = mock
7
+ Shelly::Client.stub(:new).and_return(@client)
8
+ end
9
+
10
+ it "should assign attributes" do
11
+ backup = Shelly::Backup.new(attributes)
12
+
13
+ backup.code_name.should == "foo"
14
+ backup.filename.should == "backup.tar.gz"
15
+ backup.human_size.should == "2KB"
16
+ backup.size.should == 2048
17
+ end
18
+
19
+ describe "#download" do
20
+ it "should download given backup via API file with filename to which backup will be downloaded" do
21
+ callback = lambda {}
22
+ @client.should_receive(:download_backup).with("foo", "backup.tar.gz", callback)
23
+ backup = Shelly::Backup.new(attributes)
24
+ backup.download(callback)
25
+ end
26
+ end
27
+
28
+ def attributes
29
+ {"code_name" => "foo",
30
+ "filename" => "backup.tar.gz",
31
+ "human_size" => "2KB",
32
+ "size" => 2048}
33
+ end
34
+ end
@@ -1,21 +1,19 @@
1
1
  require "spec_helper"
2
2
  require "shelly/cli/backup"
3
+ require "shelly/download_progress_bar"
3
4
 
4
5
  describe Shelly::CLI::Backup do
5
6
  before do
6
7
  @backup = Shelly::CLI::Backup.new
7
8
  @client = mock
9
+ @client.stub(:token).and_return("abc")
8
10
  Shelly::Client.stub(:new).and_return(@client)
11
+ FileUtils.mkdir_p("/projects/foo")
12
+ Dir.chdir("/projects/foo")
13
+ File.open("Cloudfile", 'w') {|f| f.write("foo-staging:\n") }
9
14
  end
10
15
 
11
16
  describe "#list" do
12
- before do
13
- FileUtils.mkdir_p("/projects/foo")
14
- Dir.chdir("/projects/foo")
15
- File.open("Cloudfile", 'w') {|f| f.write("foo-staging:\n") }
16
- @client.stub(:token).and_return("abc")
17
- end
18
-
19
17
  it "should exit with message if there is no Cloudfile" do
20
18
  File.delete("Cloudfile")
21
19
  $stdout.should_receive(:puts).with("\e[31mNo Cloudfile found\e[0m")
@@ -26,7 +24,7 @@ describe Shelly::CLI::Backup do
26
24
 
27
25
  it "should exit if user doesn't have access to cloud in Cloudfile" do
28
26
  response = {"message" => "Cloud foo-staging not found"}
29
- exception = Shelly::Client::APIError.new(response.to_json)
27
+ exception = Shelly::Client::APIError.new(response.to_json, 401)
30
28
  @client.stub(:database_backups).and_raise(exception)
31
29
  $stdout.should_receive(:puts).with(red "You have no access to 'foo-staging' cloud defined in Cloudfile")
32
30
  lambda { @backup.list }.should raise_error(SystemExit)
@@ -47,7 +45,7 @@ describe Shelly::CLI::Backup do
47
45
  end
48
46
 
49
47
  it "should take cloud from command line for which to show backups" do
50
- @client.should_receive(:database_backups).with("foo-staging").and_return([{"filename" => "backup.postgre.tar.gz", "size" => "10kb"},{"filename" => "backup.mongo.tar.gz", "size" => "22kb"}])
48
+ @client.should_receive(:database_backups).with("foo-staging").and_return([{"filename" => "backup.postgre.tar.gz", "human_size" => "10kb", "size" => 12345},{"filename" => "backup.mongo.tar.gz", "human_size" => "22kb", "size" => 333}])
51
49
  $stdout.should_receive(:puts).with(green "Available backups:")
52
50
  $stdout.should_receive(:puts).with("\n")
53
51
  $stdout.should_receive(:puts).with(" Filename | Size")
@@ -57,5 +55,65 @@ describe Shelly::CLI::Backup do
57
55
  @backup.list
58
56
  end
59
57
  end
58
+
59
+ describe "#get" do
60
+ before do
61
+ @client.stub(:download_backup)
62
+ @bar = mock(:progress_callback => @callback)
63
+ Shelly::DownloadProgressBar.stub(:new).and_return(@bar)
64
+ @client.stub(:database_backup).and_return({"filename" => "better.tar.gz", "size" => 12345})
65
+ $stdout.stub(:puts)
66
+ end
67
+
68
+ it "should make sure that cloud is choosen" do
69
+ @client.should_receive(:database_backup).with("foo-staging", "last")
70
+ @backup.get
71
+
72
+ @backup.options = {:cloud => "other"}
73
+ @client.should_receive(:database_backup).with("other", "last")
74
+ @backup.get
75
+ end
76
+
77
+ it "should fetch backup size and initialize download progress bar" do
78
+ @client.stub(:database_backup).and_return({"filename" => "backup.postgres.tar.gz", "size" => 333})
79
+ Shelly::DownloadProgressBar.should_receive(:new).with(333).and_return(@bar)
80
+
81
+ @backup.get
82
+ end
83
+
84
+ it "should fetch given backup file itself" do
85
+ @client.should_receive(:download_backup).with("foo-staging", "better.tar.gz", @bar.progress_callback)
86
+ @backup.get("better.tar.gz")
87
+ end
88
+
89
+ it "should show info where file has been saved" do
90
+ $stdout.should_receive(:puts)
91
+ $stdout.should_receive(:puts).with(green "Backup file saved to better.tar.gz")
92
+ @client.should_receive(:download_backup).with("foo-staging", "better.tar.gz", @bar.progress_callback)
93
+ @backup.get("last")
94
+ end
95
+
96
+ context "on backup not found" do
97
+ it "it should display error message" do
98
+ exception = Shelly::Client::APIError.new({}.to_json, 404)
99
+ @client.stub(:database_backup).and_raise(exception)
100
+ $stdout.should_receive(:puts).with(red "Backup not found")
101
+ $stdout.should_receive(:puts).with("You can list available backups with 'shelly backup list' command")
102
+ @backup.get("better.tar.gz")
103
+ end
104
+ end
105
+
106
+ context "on unsupported exception" do
107
+ it "should re-raise it" do
108
+ exception = Shelly::Client::APIError.new({}.to_json, 500)
109
+ @client.stub(:database_backup).and_raise(exception)
110
+ $stdout.should_not_receive(:puts).with(red "Backup not found")
111
+ $stdout.should_not_receive(:puts).with("You can list available backups with 'shelly backup list' command")
112
+ lambda {
113
+ @backup.get("better.tar.gz")
114
+ }.should raise_error(Shelly::Client::APIError)
115
+ end
116
+ end
117
+ end
60
118
  end
61
119
  end
@@ -34,7 +34,7 @@ describe Shelly::CLI::Config do
34
34
 
35
35
  it "should exit if user doesn't have access to cloud in Cloudfile" do
36
36
  response = {"message" => "Cloud foo-staging not found"}
37
- exception = Shelly::Client::APIError.new(response.to_json)
37
+ exception = Shelly::Client::APIError.new(response.to_json, 404)
38
38
  @client.stub(:app_configs).and_raise(exception)
39
39
  $stdout.should_receive(:puts).with(red "You have no access to 'foo-production' cloud defined in Cloudfile")
40
40
  lambda { @config.list }.should raise_error(SystemExit)
@@ -29,7 +29,7 @@ describe Shelly::CLI::Deploys do
29
29
 
30
30
  it "should exit if user doesn't have access to cloud in Cloudfile" do
31
31
  response = {"message" => "Cloud foo-staging not found"}
32
- exception = Shelly::Client::APIError.new(response.to_json)
32
+ exception = Shelly::Client::APIError.new(response.to_json, 404)
33
33
  @client.stub(:deploy_logs).and_raise(exception)
34
34
  $stdout.should_receive(:puts).with(red "You have no access to 'foo-staging' cloud defined in Cloudfile")
35
35
  lambda { @deploys.list }.should raise_error(SystemExit)
@@ -87,7 +87,7 @@ describe Shelly::CLI::Deploys do
87
87
 
88
88
  it "should exit if user doesn't have access to cloud in Cloudfile" do
89
89
  response = {"message" => "Cloud foo-staging not found"}
90
- exception = Shelly::Client::APIError.new(response.to_json)
90
+ exception = Shelly::Client::APIError.new(response.to_json, 404)
91
91
  @client.stub(:deploy_log).and_raise(exception)
92
92
  $stdout.should_receive(:puts).with(red "You have no access to 'foo-staging' cloud defined in Cloudfile")
93
93
  lambda { @deploys.show("last") }.should raise_error(SystemExit)
@@ -172,7 +172,7 @@ OUT
172
172
  context "on unsuccessful registration" do
173
173
  it "should display errors and exit with 1" do
174
174
  response = {"message" => "Validation Failed", "errors" => [["email", "has been already taken"]]}
175
- exception = Shelly::Client::APIError.new(response.to_json)
175
+ exception = Shelly::Client::APIError.new(response.to_json, 422)
176
176
  @client.stub(:register_user).and_raise(exception)
177
177
  $stdout.should_receive(:puts).with("\e[31mEmail has been already taken\e[0m")
178
178
  lambda {
@@ -259,7 +259,7 @@ OUT
259
259
  context "on unauthorized user" do
260
260
  it "should exit with 1 and display error message" do
261
261
  response = {"message" => "Unauthorized", "url" => "https://admin.winniecloud.com/users/password/new"}
262
- exception = Shelly::Client::APIError.new(response.to_json)
262
+ exception = Shelly::Client::APIError.new(response.to_json, 401)
263
263
  @client.stub(:token).and_raise(exception)
264
264
  $stdout.should_receive(:puts).with("\e[31mWrong email or password\e[0m")
265
265
  $stdout.should_receive(:puts).with("\e[31mYou can reset password by using link:\e[0m")
@@ -386,7 +386,7 @@ OUT
386
386
 
387
387
  it "should display validation errors if they are any" do
388
388
  response = {"message" => "Validation Failed", "errors" => [["code_name", "has been already taken"]]}
389
- exception = Shelly::Client::APIError.new(response.to_json)
389
+ exception = Shelly::Client::APIError.new(response.to_json, 422)
390
390
  @app.should_receive(:create).and_raise(exception)
391
391
  $stdout.should_receive(:puts).with("\e[31mCode name has been already taken\e[0m")
392
392
  $stdout.should_receive(:puts).with("\e[31mFix erros in the below command and type it again to create your cloud\e[0m")
@@ -475,7 +475,7 @@ OUT
475
475
  context "on failure" do
476
476
  it "should display info that user is not logged in" do
477
477
  body = {"message" => "Unauthorized"}
478
- error = Shelly::Client::APIError.new(body.to_json)
478
+ error = Shelly::Client::APIError.new(body.to_json, 401)
479
479
  @client.stub(:token).and_raise(error)
480
480
  $stdout.should_receive(:puts).with("\e[31mYou are not logged in, use `shelly login`\e[0m")
481
481
  lambda {
@@ -508,7 +508,7 @@ OUT
508
508
 
509
509
  it "should exit if user doesn't have access to clouds in Cloudfile" do
510
510
  response = {"message" => "Cloud foo-production not found"}
511
- exception = Shelly::Client::APIError.new(response.to_json)
511
+ exception = Shelly::Client::APIError.new(response.to_json, 404)
512
512
  @client.stub(:start_cloud).and_raise(exception)
513
513
  $stdout.should_receive(:puts).with(red "You have no access to 'foo-production' cloud defined in Cloudfile")
514
514
  lambda { @main.start }.should raise_error(SystemExit)
@@ -516,7 +516,7 @@ OUT
516
516
 
517
517
  it "should exit if user is not logged in" do
518
518
  response = {"message" => "Unauthorized"}
519
- exception = Shelly::Client::APIError.new(response.to_json)
519
+ exception = Shelly::Client::APIError.new(response.to_json, 401)
520
520
  @client.stub(:token).and_raise(exception)
521
521
  $stdout.should_receive(:puts).with(red "You are not logged in. To log in use:")
522
522
  $stdout.should_receive(:puts).with(" shelly login")
@@ -626,7 +626,7 @@ OUT
626
626
 
627
627
  it "should exit if user doesn't have access to clouds in Cloudfile" do
628
628
  response = {"message" => "Cloud foo-production not found"}
629
- exception = Shelly::Client::APIError.new(response.to_json)
629
+ exception = Shelly::Client::APIError.new(response.to_json, 404)
630
630
  @client.stub(:stop_cloud).and_raise(exception)
631
631
  $stdout.should_receive(:puts).with(red "You have no access to 'foo-production' cloud defined in Cloudfile")
632
632
  lambda { @main.stop }.should raise_error(SystemExit)
@@ -634,7 +634,7 @@ OUT
634
634
 
635
635
  it "should exit if user is not logged in" do
636
636
  response = {"message" => "Unauthorized"}
637
- exception = Shelly::Client::APIError.new(response.to_json)
637
+ exception = Shelly::Client::APIError.new(response.to_json, 401)
638
638
  @client.stub(:token).and_raise(exception)
639
639
  $stdout.should_receive(:puts).with(red "You are not logged in. To log in use:")
640
640
  $stdout.should_receive(:puts).with(" shelly login")
@@ -717,7 +717,7 @@ OUT
717
717
 
718
718
  it "should raise an error if user does not have access to cloud" do
719
719
  response = {"message" => "Cloud foo-staging not found"}
720
- exception = Shelly::Client::APIError.new(response.to_json)
720
+ exception = Shelly::Client::APIError.new(response.to_json, 404)
721
721
  @client.stub(:app_ips).and_raise(exception)
722
722
  $stdout.should_receive(:puts).with(red "You have no access to 'foo-staging' cloud defined in Cloudfile")
723
723
  @main.ip
@@ -794,7 +794,7 @@ OUT
794
794
 
795
795
  it "should raise Client::APIError" do
796
796
  response = {:message => "Application not found"}
797
- exception = Shelly::Client::APIError.new(response.to_json)
797
+ exception = Shelly::Client::APIError.new(response.to_json, 404)
798
798
  @app.stub(:delete).and_raise(exception)
799
799
  $stdout.should_receive(:puts).with("\e[31mApplication not found\e[0m")
800
800
  lambda{
@@ -854,7 +854,7 @@ OUT
854
854
 
855
855
  it "should exit if user doesn't have access to clouds in Cloudfile" do
856
856
  response = {"message" => "Cloud foo-production not found"}
857
- exception = Shelly::Client::APIError.new(response.to_json)
857
+ exception = Shelly::Client::APIError.new(response.to_json, 404)
858
858
  @client.stub(:application_logs).and_raise(exception)
859
859
  $stdout.should_receive(:puts).
860
860
  with(red "You have no access to cloud 'foo-production'")
@@ -863,7 +863,7 @@ OUT
863
863
 
864
864
  it "should exit if user is not logged in" do
865
865
  response = {"message" => "Unauthorized"}
866
- exception = Shelly::Client::APIError.new(response.to_json)
866
+ exception = Shelly::Client::APIError.new(response.to_json, 401)
867
867
  @client.stub(:token).and_raise(exception)
868
868
  $stdout.should_receive(:puts).
869
869
  with(red "You are not logged in. To log in use:")
@@ -69,7 +69,7 @@ describe Shelly::CLI::User do
69
69
  context "on failure" do
70
70
  it "should raise an error if user does not have access to cloud" do
71
71
  response = {"message" => "Cloud foo-staging not found"}
72
- exception = Shelly::Client::APIError.new(response.to_json)
72
+ exception = Shelly::Client::APIError.new(response.to_json, 404)
73
73
  @client.stub(:app_users).and_raise(exception)
74
74
  $stdout.should_receive(:puts).with(red "You have no access to 'foo-staging' cloud defined in Cloudfile")
75
75
  @cli_user.list
@@ -130,7 +130,7 @@ describe Shelly::CLI::User do
130
130
  context "on failure" do
131
131
  it "should raise error if user doesnt have access to cloud" do
132
132
  response = {"message" => "Cloud foo-staging not found"}
133
- exception = Shelly::Client::APIError.new(response.to_json)
133
+ exception = Shelly::Client::APIError.new(response.to_json, 404)
134
134
  @client.stub(:send_invitation).and_raise(exception)
135
135
  $stdout.should_receive(:puts).with(red "You have no access to 'foo-staging' cloud defined in Cloudfile")
136
136
  @cli_user.add("megan@example.com")
@@ -3,7 +3,7 @@ require "spec_helper"
3
3
  describe Shelly::Client::APIError do
4
4
  before do
5
5
  body = {"message" => "something went wrong", "errors" => [["first", "foo"]], "url" => "https://foo.bar"}
6
- @error = Shelly::Client::APIError.new(body.to_json)
6
+ @error = Shelly::Client::APIError.new(body.to_json, 404)
7
7
  end
8
8
 
9
9
  it "should return error message" do
@@ -21,12 +21,23 @@ describe Shelly::Client::APIError do
21
21
  it "should return user friendly string" do
22
22
  @error.each_error{|error| error.should == "First foo"}
23
23
  end
24
+
25
+ describe "#not_found?" do
26
+ it "should return true if response status code is 404" do
27
+ @error.should be_not_found
28
+ end
29
+
30
+ it "should return false if response status code is not 404" do
31
+ error = Shelly::Client::APIError.new({}.to_json, 500)
32
+ error.should_not be_not_found
33
+ end
34
+ end
24
35
 
25
36
  describe "#validation?" do
26
37
  context "when error is caused by validation errors" do
27
38
  it "should return true" do
28
39
  body = {"message" => "Validation Failed"}
29
- error = Shelly::Client::APIError.new(body.to_json)
40
+ error = Shelly::Client::APIError.new(body.to_json, 422)
30
41
  error.should be_validation
31
42
  end
32
43
  end
@@ -42,7 +53,7 @@ describe Shelly::Client::APIError do
42
53
  context "when error is caused by unauthorized error" do
43
54
  it "should return true" do
44
55
  body = {"message" => "Unauthorized"}
45
- error = Shelly::Client::APIError.new(body.to_json)
56
+ error = Shelly::Client::APIError.new(body.to_json, 401)
46
57
  error.should be_unauthorized
47
58
  end
48
59
  end
@@ -59,7 +70,11 @@ describe Shelly::Client do
59
70
  before do
60
71
  ENV['SHELLY_URL'] = nil
61
72
  @client = Shelly::Client.new("bob@example.com", "secret")
62
- @url = "https://#{CGI.escape("bob@example.com")}:secret@admin.winniecloud.com/apiv2"
73
+ end
74
+
75
+ def api_url(resource = "")
76
+ auth = "#{CGI.escape(@client.email)}:#{@client.password}@"
77
+ "https://#{auth}admin.winniecloud.com/apiv2/#{resource}"
63
78
  end
64
79
 
65
80
  describe "#api_url" do
@@ -103,7 +118,7 @@ describe Shelly::Client do
103
118
  describe "#deploy_logs" do
104
119
  it "should send get request" do
105
120
  time = Time.now
106
- FakeWeb.register_uri(:get, @url + "/apps/staging-foo/deploys", :body => [{:failed => false, :created_at => time},
121
+ FakeWeb.register_uri(:get, api_url("apps/staging-foo/deploys"), :body => [{:failed => false, :created_at => time},
107
122
  {:failed => true, :created_at => time+1}].to_json)
108
123
  response = @client.deploy_logs("staging-foo")
109
124
  response.should == [{"failed"=>false, "created_at"=>time.to_s},
@@ -113,7 +128,7 @@ describe Shelly::Client do
113
128
 
114
129
  describe "#deploy_log" do
115
130
  it "should send get request with cloud and log" do
116
- FakeWeb.register_uri(:get, @url + "/apps/staging-foo/deploys/2011-11-29-11-50-16", :body => {:content => "Log"}.to_json)
131
+ FakeWeb.register_uri(:get, api_url("apps/staging-foo/deploys/2011-11-29-11-50-16"), :body => {:content => "Log"}.to_json)
117
132
  response = @client.deploy_log("staging-foo", "2011-11-29-11-50-16")
118
133
  response.should == {"content" => "Log"}
119
134
  end
@@ -121,7 +136,7 @@ describe Shelly::Client do
121
136
 
122
137
  describe "#app_configs" do
123
138
  it "should send get request" do
124
- FakeWeb.register_uri(:get, @url + "/apps/staging-foo/configs", :body => [{:created_by_user => true, :path => "config/app.yml"}].to_json)
139
+ FakeWeb.register_uri(:get, api_url("apps/staging-foo/configs"), :body => [{:created_by_user => true, :path => "config/app.yml"}].to_json)
125
140
  response = @client.app_configs("staging-foo")
126
141
  response.should == [{"created_by_user" => true, "path" => "config/app.yml"}]
127
142
  end
@@ -130,7 +145,7 @@ describe Shelly::Client do
130
145
  describe "#application_logs" do
131
146
  it "should send get request" do
132
147
  time = Time.now
133
- FakeWeb.register_uri(:get, @url + "/apps/staging-foo/logs",
148
+ FakeWeb.register_uri(:get, api_url("apps/staging-foo/logs"),
134
149
  :body => {:logs => ["application_log_1", "application_log_2"]}.to_json)
135
150
  response = @client.application_logs("staging-foo")
136
151
  response.should == {"logs" => ["application_log_1", "application_log_2"]}
@@ -146,7 +161,7 @@ describe Shelly::Client do
146
161
 
147
162
  describe "#app_users" do
148
163
  it "should send get request with app code_names" do
149
- FakeWeb.register_uri(:get, @url + "/apps/staging-foo/users", :body => [{:email => "test@example.com"},
164
+ FakeWeb.register_uri(:get, api_url("apps/staging-foo/users"), :body => [{:email => "test@example.com"},
150
165
  {:email => "test2@example.com"}].to_json)
151
166
  response = @client.app_users("staging-foo")
152
167
  response.should == [{"email" => "test@example.com"}, {"email" => "test2@example.com"}]
@@ -155,7 +170,7 @@ describe Shelly::Client do
155
170
 
156
171
  describe "#app_ips" do
157
172
  it "should send get request with app code_name" do
158
- FakeWeb.register_uri(:get, @url + "/apps/staging-foo/ips", :body => {:mail_server_ip => "10.0.1.1", :web_server_ip => "88.198.21.187"}.to_json)
173
+ FakeWeb.register_uri(:get, api_url("apps/staging-foo/ips"), :body => {:mail_server_ip => "10.0.1.1", :web_server_ip => "88.198.21.187"}.to_json)
159
174
  response = @client.app_ips("staging-foo")
160
175
  response.should == {"mail_server_ip" => "10.0.1.1", "web_server_ip" => "88.198.21.187"}
161
176
  end
@@ -163,8 +178,8 @@ describe Shelly::Client do
163
178
 
164
179
  describe "#send_invitation" do
165
180
  it "should send post with developer's email" do
166
- FakeWeb.register_uri(:post, @url + "/apps/staging-foo/collaborations", :body => {}.to_json)
167
- FakeWeb.register_uri(:post, @url + "/apps/production-foo/collaborations", :body => {}.to_json)
181
+ FakeWeb.register_uri(:post, api_url("apps/staging-foo/collaborations"), :body => {}.to_json)
182
+ FakeWeb.register_uri(:post, api_url("apps/production-foo/collaborations"), :body => {}.to_json)
168
183
  response = @client.send_invitation("staging-foo", "megan@example.com")
169
184
  response.should == {}
170
185
  end
@@ -186,7 +201,7 @@ describe Shelly::Client do
186
201
 
187
202
  describe "#start_cloud" do
188
203
  it "should sent post request with cloud's code_name" do
189
- FakeWeb.register_uri(:put, @url + "/apps/staging-foo/start", :body => {}.to_json)
204
+ FakeWeb.register_uri(:put, api_url("apps/staging-foo/start"), :body => {}.to_json)
190
205
  response = @client.start_cloud("staging-foo")
191
206
  response.should == {}
192
207
  end
@@ -194,7 +209,7 @@ describe Shelly::Client do
194
209
 
195
210
  describe "#stop_cloud" do
196
211
  it "should sent delete request with cloud's code_name" do
197
- FakeWeb.register_uri(:put, @url + "/apps/staging-foo/stop", :body => {}.to_json)
212
+ FakeWeb.register_uri(:put, api_url("apps/staging-foo/stop"), :body => {}.to_json)
198
213
  response = @client.stop_cloud("staging-foo")
199
214
  response.should == {}
200
215
  end
@@ -206,6 +221,48 @@ describe Shelly::Client do
206
221
  @client.ssh_key_available?("ssh-key Abb")
207
222
  end
208
223
  end
224
+
225
+ describe "#database_backup" do
226
+ it "should fetch backup description from API" do
227
+ expected = {
228
+ "filename" => @filename,
229
+ "size" => 1234,
230
+ "human_size" => "2KB"
231
+ }
232
+ filename = "2011.11.26.04.00.10.foo.postgres.tar.gz"
233
+ url = api_url("apps/foo/database_backups/#{filename}")
234
+ FakeWeb.register_uri(:get, url, :body => expected.to_json)
235
+
236
+ @client.database_backup("foo", filename).should == expected
237
+ end
238
+ end
239
+
240
+ describe "#download_backup" do
241
+ before do
242
+ @filename = "2011.11.26.04.00.10.foo.postgres.tar.gz"
243
+ url = api_url("apps/foo/database_backups/#{@filename}")
244
+ response = Net::HTTPResponse.new('', '', '')
245
+ # Streaming
246
+ response.stub(:read_body).and_yield("aaa").and_yield("bbbbb").and_yield("dddf")
247
+ FakeWeb.register_uri(:get, url, :response => response)
248
+ end
249
+
250
+ it "should write streamed database backup to file" do
251
+ @client.download_backup("foo", @filename)
252
+ File.read(@filename).should == %w(aaa bbbbb dddf).join
253
+ end
254
+
255
+ it "should execute progress_callback with size of every chunk" do
256
+ progress = mock(:update => true)
257
+ progress.should_receive(:update).with(3)
258
+ progress.should_receive(:update).with(5)
259
+ progress.should_receive(:update).with(4)
260
+
261
+ callback = lambda { |size| progress.update(size) }
262
+
263
+ @client.download_backup("foo", @filename, callback)
264
+ end
265
+ end
209
266
 
210
267
  describe "#request_parameters" do
211
268
  it "should return hash of resquest parameters" do
@@ -0,0 +1,28 @@
1
+ require "spec_helper"
2
+ require "shelly/download_progress_bar"
3
+
4
+ describe Shelly::DownloadProgressBar do
5
+ before do
6
+ $stderr.stub(:print)
7
+ @bar = Shelly::DownloadProgressBar.new(4444)
8
+ end
9
+
10
+ it "should inherith from ProgressBar" do
11
+ @bar.should be_kind_of(ProgressBar)
12
+ end
13
+
14
+ it "should initialize parent with header and given size" do
15
+ @bar.title.should == "Progress"
16
+ @bar.total.should == 4444
17
+ end
18
+
19
+ describe "#progress_callback" do
20
+ it "should return callback for updating progress bar" do
21
+ @bar.should_receive(:inc).with(10)
22
+ @bar.should_receive(:inc).with(20)
23
+
24
+ @bar.progress_callback.call(10)
25
+ @bar.progress_callback.call(20)
26
+ end
27
+ end
28
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shelly
3
3
  version: !ruby/object:Gem::Version
4
- hash: 93
4
+ hash: 91
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 33
10
- version: 0.0.33
9
+ - 34
10
+ version: 0.0.34
11
11
  platform: ruby
12
12
  authors:
13
13
  - Shelly Cloud team
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-12-26 00:00:00 Z
18
+ date: 2011-12-27 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: rspec
@@ -187,6 +187,20 @@ dependencies:
187
187
  version: "0"
188
188
  type: :runtime
189
189
  version_requirements: *id012
190
+ - !ruby/object:Gem::Dependency
191
+ name: progressbar
192
+ prerelease: false
193
+ requirement: &id013 !ruby/object:Gem::Requirement
194
+ none: false
195
+ requirements:
196
+ - - ">="
197
+ - !ruby/object:Gem::Version
198
+ hash: 3
199
+ segments:
200
+ - 0
201
+ version: "0"
202
+ type: :runtime
203
+ version_requirements: *id013
190
204
  description: Tool for managing applications and clouds at shellycloud.com
191
205
  email:
192
206
  - support@shellycloud.com
@@ -208,6 +222,7 @@ files:
208
222
  - lib/core_ext/object.rb
209
223
  - lib/shelly.rb
210
224
  - lib/shelly/app.rb
225
+ - lib/shelly/backup.rb
211
226
  - lib/shelly/cli/backup.rb
212
227
  - lib/shelly/cli/command.rb
213
228
  - lib/shelly/cli/config.rb
@@ -217,6 +232,7 @@ files:
217
232
  - lib/shelly/cli/user.rb
218
233
  - lib/shelly/client.rb
219
234
  - lib/shelly/cloudfile.rb
235
+ - lib/shelly/download_progress_bar.rb
220
236
  - lib/shelly/helpers.rb
221
237
  - lib/shelly/model.rb
222
238
  - lib/shelly/templates/Cloudfile.erb
@@ -229,6 +245,7 @@ files:
229
245
  - spec/helpers.rb
230
246
  - spec/input_faker.rb
231
247
  - spec/shelly/app_spec.rb
248
+ - spec/shelly/backup_spec.rb
232
249
  - spec/shelly/cli/backup_spec.rb
233
250
  - spec/shelly/cli/config_spec.rb
234
251
  - spec/shelly/cli/deploys_spec.rb
@@ -237,6 +254,7 @@ files:
237
254
  - spec/shelly/cli/user_spec.rb
238
255
  - spec/shelly/client_spec.rb
239
256
  - spec/shelly/cloudfile_spec.rb
257
+ - spec/shelly/download_progress_bar_spec.rb
240
258
  - spec/shelly/model_spec.rb
241
259
  - spec/shelly/user_spec.rb
242
260
  - spec/spec_helper.rb
@@ -278,6 +296,7 @@ test_files:
278
296
  - spec/helpers.rb
279
297
  - spec/input_faker.rb
280
298
  - spec/shelly/app_spec.rb
299
+ - spec/shelly/backup_spec.rb
281
300
  - spec/shelly/cli/backup_spec.rb
282
301
  - spec/shelly/cli/config_spec.rb
283
302
  - spec/shelly/cli/deploys_spec.rb
@@ -286,6 +305,7 @@ test_files:
286
305
  - spec/shelly/cli/user_spec.rb
287
306
  - spec/shelly/client_spec.rb
288
307
  - spec/shelly/cloudfile_spec.rb
308
+ - spec/shelly/download_progress_bar_spec.rb
289
309
  - spec/shelly/model_spec.rb
290
310
  - spec/shelly/user_spec.rb
291
311
  - spec/spec_helper.rb