shelly 0.0.33 → 0.0.34

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