shelly 0.0.28 → 0.0.29

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,3 @@
1
1
  module Shelly
2
- VERSION = "0.0.28"
2
+ VERSION = "0.0.29"
3
3
  end
@@ -0,0 +1,9 @@
1
+ class Thor
2
+ module Shell
3
+
4
+ def print_wrapped(*args)
5
+ shell.print_wrapped(*args)
6
+ end
7
+
8
+ end
9
+ end
@@ -9,4 +9,3 @@ class Thor
9
9
 
10
10
  end
11
11
  end
12
-
@@ -3,5 +3,13 @@ module RSpec
3
3
  def fake_stdin(strings)
4
4
  InputFaker.with_fake_input(strings) { yield }
5
5
  end
6
+
7
+ def green(string)
8
+ "\e[32m#{string}\e[0m"
9
+ end
10
+
11
+ def red(string)
12
+ "\e[31m#{string}\e[0m"
13
+ end
6
14
  end
7
15
  end
@@ -18,9 +18,16 @@ describe Shelly::App do
18
18
  end
19
19
 
20
20
  describe "#users" do
21
- it "should " do
22
- @client.should_receive(:app_users).with(["staging-foo", "production-foo"])
23
- @app.users(["staging-foo", "production-foo"])
21
+ it "should fetch app's users" do
22
+ @client.should_receive(:app_users).with("foo-staging")
23
+ @app.users
24
+ end
25
+ end
26
+
27
+ describe "#ips" do
28
+ it "should get app's ips" do
29
+ @client.should_receive(:app_ips).with("foo-staging")
30
+ @app.ips
24
31
  end
25
32
  end
26
33
 
@@ -110,6 +117,25 @@ config
110
117
  end
111
118
  end
112
119
 
120
+ describe "#start & #stop" do
121
+ it "should start cloud" do
122
+ @client.should_receive(:start_cloud).with("foo-staging")
123
+ @app.start
124
+ end
125
+
126
+ it "should stop cloud" do
127
+ @client.should_receive(:stop_cloud).with("foo-staging")
128
+ @app.stop
129
+ end
130
+ end
131
+
132
+ describe "#logs" do
133
+ it "should list logs" do
134
+ @client.should_receive(:cloud_logs).with("foo-staging")
135
+ @app.logs
136
+ end
137
+ end
138
+
113
139
  describe "#create" do
114
140
  context "without providing domain" do
115
141
  it "should create the app on shelly cloud via API client" do
@@ -0,0 +1,73 @@
1
+ require "spec_helper"
2
+ require "shelly/cli/deploys"
3
+
4
+ describe Shelly::CLI::Deploys do
5
+ before do
6
+ FileUtils.stub(:chmod)
7
+ @deploys = Shelly::CLI::Deploys.new
8
+ @client = mock
9
+ Shelly::Client.stub(:new).and_return(@client)
10
+ $stdout.stub(:puts)
11
+ $stdout.stub(:print)
12
+ end
13
+
14
+ describe "#list" do
15
+ before do
16
+ FileUtils.mkdir_p("/projects/foo")
17
+ Dir.chdir("/projects/foo")
18
+ File.open("Cloudfile", 'w') {|f| f.write("foo-staging:\n") }
19
+ @client.stub(:token).and_return("abc")
20
+ end
21
+
22
+ it "should exit with message if there is no Cloudfile" do
23
+ File.delete("Cloudfile")
24
+ $stdout.should_receive(:puts).with("\e[31mNo Cloudfile found\e[0m")
25
+ lambda {
26
+ @deploys.list
27
+ }.should raise_error(SystemExit)
28
+ end
29
+
30
+ it "should exit if user doesn't have access to cloud in Cloudfile" do
31
+ response = {"message" => "Cloud foo-staging not found"}
32
+ exception = Shelly::Client::APIError.new(response.to_json)
33
+ @client.stub(:cloud_logs).and_raise(exception)
34
+ $stdout.should_receive(:puts).with(red "You have no access to 'foo-staging' cloud defined in Cloudfile")
35
+ lambda { @deploys.list }.should raise_error(SystemExit)
36
+ end
37
+
38
+ context "multiple clouds" do
39
+ before do
40
+ File.open("Cloudfile", 'w') {|f| f.write("foo-staging:\nfoo-production:\n") }
41
+ end
42
+
43
+ it "should show information to select specific cloud and exit" do
44
+ $stdout.should_receive(:puts).with("You have multiple clouds in Cloudfile. Select cloud to view deploy logs using:")
45
+ $stdout.should_receive(:puts).with(" shelly deploy list foo-production")
46
+ $stdout.should_receive(:puts).with("Available clouds:")
47
+ $stdout.should_receive(:puts).with(" * foo-production")
48
+ $stdout.should_receive(:puts).with(" * foo-staging")
49
+ lambda { @deploys.list }.should raise_error(SystemExit)
50
+ end
51
+
52
+ it "should take cloud from command line for which to show logs" do
53
+ @client.should_receive(:cloud_logs).with("foo-staging").and_return([{"failed" => false, "created_at" => "2011-12-12-14-14-59"}])
54
+ $stdout.should_receive(:puts).with(green "Available deploy logs")
55
+ $stdout.should_receive(:puts).with(" * 2011-12-12-14-14-59")
56
+ @deploys.list("foo-staging")
57
+ end
58
+ end
59
+
60
+ context "single cloud" do
61
+ it "should display available logs" do
62
+ @client.should_receive(:cloud_logs).with("foo-staging").and_return([{"failed" => false, "created_at" => "2011-12-12-14-14-59"}, {"failed" => true, "created_at" => "2011-12-12-15-14-59"}])
63
+ $stdout.should_receive(:puts).with(green "Available deploy logs")
64
+ $stdout.should_receive(:puts).with(" * 2011-12-12-14-14-59")
65
+ $stdout.should_receive(:puts).with(" * 2011-12-12-15-14-59 (failed)")
66
+ @deploys.list
67
+ end
68
+
69
+ end
70
+
71
+ end
72
+
73
+ end
@@ -23,14 +23,18 @@ describe Shelly::CLI::Main do
23
23
  it "should display available commands" do
24
24
  expected = <<-OUT
25
25
  Tasks:
26
- shelly add # Adds new cloud to Shelly Cloud
27
- shelly help [TASK] # Describe available tasks or one specific task
28
- shelly ip # Lists clouds IP's
29
- shelly list # Lists all your clouds
30
- shelly login [EMAIL] # Logs user in to Shelly Cloud
31
- shelly register [EMAIL] # Registers new user account on Shelly Cloud
32
- shelly user <command> # Manages users using this cloud
33
- shelly version # Displays shelly version
26
+ shelly add # Adds new cloud to Shelly Cloud
27
+ shelly delete CODE-NAME # Delete cloud from Shelly Cloud
28
+ shelly deploys <command> # View cloud deploy logs
29
+ shelly help [TASK] # Describe available tasks or one specific task
30
+ shelly ip # Lists clouds IP's
31
+ shelly list # Lists all your clouds
32
+ shelly login [EMAIL] # Logs user in to Shelly Cloud
33
+ shelly register [EMAIL] # Registers new user account on Shelly Cloud
34
+ shelly start [CODE-NAME] # Starts specific cloud
35
+ shelly stop [CODE-NAME] # Stops specific cloud
36
+ shelly user <command> # Manages users using this cloud
37
+ shelly version # Displays shelly version
34
38
 
35
39
  Options:
36
40
  [--debug] # Show debug information
@@ -165,6 +169,9 @@ OUT
165
169
  describe "#login" do
166
170
  before do
167
171
  @user = Shelly::User.new
172
+ @key_path = File.expand_path("~/.ssh/id_rsa.pub")
173
+ FileUtils.mkdir_p("~/.ssh")
174
+ File.open("~/.ssh/id_rsa.pub", "w") { |f| f << "ssh-key AAbbcc" }
168
175
  @user.stub(:upload_ssh_key)
169
176
  @client.stub(:token).and_return("abc")
170
177
  @client.stub(:apps).and_return([{"code_name" => "abc", "state" => "running"},
@@ -186,6 +193,13 @@ OUT
186
193
  end
187
194
  end
188
195
 
196
+ it "should accept email as parameter" do
197
+ $stdout.should_receive(:puts).with("Login successful")
198
+ fake_stdin(["secret"]) do
199
+ @main.login("megan@example.com")
200
+ end
201
+ end
202
+
189
203
  it "should upload user's public SSH key" do
190
204
  @user.should_receive(:upload_ssh_key)
191
205
  $stdout.should_receive(:puts).with("Uploading your public SSH key")
@@ -212,6 +226,18 @@ OUT
212
226
  end
213
227
  end
214
228
 
229
+ context "when local ssh key doesn't exists" do
230
+ it "should display error message and return exit with 1" do
231
+ FileUtils.rm_rf(@key_path)
232
+ File.exists?(@key_path).should be_false
233
+ $stdout.should_receive(:puts).with("\e[31mNo such file or directory - " + @key_path + "\e[0m")
234
+ $stdout.should_receive(:puts).with("\e[31mUse ssh-keygen to generate ssh key pair\e[0m")
235
+ lambda {
236
+ @main.login
237
+ }.should raise_error(SystemExit)
238
+ end
239
+ end
240
+
215
241
  context "on unauthorized user" do
216
242
  it "should exit with 1 and display error message" do
217
243
  response = {"message" => "Unauthorized", "url" => "https://admin.winniecloud.com/users/password/new"}
@@ -439,7 +465,191 @@ OUT
439
465
  }.should raise_error(SystemExit)
440
466
  end
441
467
  end
468
+ end
442
469
 
470
+ describe "#start" do
471
+ before do
472
+ @user = Shelly::User.new
473
+ @client.stub(:token).and_return("abc")
474
+ FileUtils.mkdir_p("/projects/foo")
475
+ Dir.chdir("/projects/foo")
476
+ File.open("Cloudfile", 'w') {|f| f.write("foo-production:\n") }
477
+ Shelly::User.stub(:new).and_return(@user)
478
+ @client.stub(:apps).and_return([{"code_name" => "foo-production"}, {"code_name" => "foo-staging"}])
479
+ @app = Shelly::App.new
480
+ Shelly::App.stub(:new).and_return(@app)
481
+ end
482
+
483
+ it "should exit if there is no Cloudfile" do
484
+ File.delete("Cloudfile")
485
+ $stdout.should_receive(:puts).with("\e[31mNo Cloudfile found\e[0m")
486
+ lambda {
487
+ @main.start
488
+ }.should raise_error(SystemExit)
489
+ end
490
+
491
+ it "should exit if user doesn't have access to clouds in Cloudfile" do
492
+ response = {"message" => "Cloud foo-production not found"}
493
+ exception = Shelly::Client::APIError.new(response.to_json)
494
+ @client.stub(:start_cloud).and_raise(exception)
495
+ $stdout.should_receive(:puts).with(red "You have no access to 'foo-production' cloud defined in Cloudfile")
496
+ lambda { @main.start }.should raise_error(SystemExit)
497
+ end
498
+
499
+ it "should exit if user is not logged in" do
500
+ response = {"message" => "Unauthorized"}
501
+ exception = Shelly::Client::APIError.new(response.to_json)
502
+ @client.stub(:token).and_raise(exception)
503
+ $stdout.should_receive(:puts).with(red "You are not logged in. To log in use:")
504
+ $stdout.should_receive(:puts).with(" shelly login")
505
+ lambda { @main.start }.should raise_error(SystemExit)
506
+ end
507
+
508
+ context "single cloud in Cloudfile" do
509
+ it "should start the cloud" do
510
+ @client.stub(:start_cloud)
511
+ $stdout.should_receive(:puts).with(green "Starting cloud foo-production. Check status with:")
512
+ $stdout.should_receive(:puts).with(" shelly list")
513
+ @main.start
514
+ end
515
+ end
516
+
517
+ context "multiple clouds in Cloudfile" do
518
+ before do
519
+ File.open("Cloudfile", 'w') {|f| f.write("foo-staging:\nfoo-production:\n") }
520
+ end
521
+
522
+ it "should show information to start specific cloud and exit" do
523
+ $stdout.should_receive(:puts).with("You have multiple clouds in Cloudfile. Select which to start using:")
524
+ $stdout.should_receive(:puts).with(" shelly start foo-production")
525
+ $stdout.should_receive(:puts).with("Available clouds:")
526
+ $stdout.should_receive(:puts).with(" * foo-production")
527
+ $stdout.should_receive(:puts).with(" * foo-staging")
528
+ lambda { @main.start }.should raise_error(SystemExit)
529
+ end
530
+
531
+ it "should fetch from command line which cloud to start" do
532
+ @client.should_receive(:start_cloud).with("foo-staging")
533
+ $stdout.should_receive(:puts).with(green "Starting cloud foo-staging. Check status with:")
534
+ $stdout.should_receive(:puts).with(" shelly list")
535
+ @main.start("foo-staging")
536
+ end
537
+ end
538
+
539
+ context "on failure" do
540
+ it "should show information that cloud is running" do
541
+ raise_conflict(:state => "running")
542
+ $stdout.should_receive(:puts).with(red "Not starting: cloud 'foo-production' is already running")
543
+ lambda { @main.start }.should raise_error(SystemExit)
544
+ end
545
+
546
+ %w{deploying configuring}.each do |state|
547
+ it "should show information that cloud is #{state}" do
548
+ raise_conflict(:state => state)
549
+ $stdout.should_receive(:puts).with(red "Not starting: cloud 'foo-production' is currently deploying")
550
+ lambda { @main.start }.should raise_error(SystemExit)
551
+ end
552
+ end
553
+
554
+ it "should show information that cloud has no code" do
555
+ raise_conflict(:state => "no_code")
556
+ $stdout.should_receive(:puts).with(red "Not starting: no source code provided")
557
+ $stdout.should_receive(:puts).with(red "Push source code using:")
558
+ $stdout.should_receive(:puts).with(" git push production master")
559
+ lambda { @main.start }.should raise_error(SystemExit)
560
+ end
561
+
562
+ %w{deploy_failed configuration_failed}.each do |state|
563
+ it "should show information that cloud #{state}" do
564
+ raise_conflict(:state => state, :link => "http://example.com/logs")
565
+ $stdout.should_receive(:puts).with(red "Not starting: deployment failed")
566
+ $stdout.should_receive(:puts).with(red "Support has been notified")
567
+ $stdout.should_receive(:puts).with(red "See http://example.com/logs for reasons of failure")
568
+ lambda { @main.start }.should raise_error(SystemExit)
569
+ end
570
+ end
571
+ it "should open billing page" do
572
+ raise_conflict(:state => "no_billing")
573
+ $stdout.should_receive(:puts).with(red "Please fill in billing details to start foo-production. Opening browser.")
574
+ @app.should_receive(:open_billing_page)
575
+ lambda { @main.start }.should raise_error(SystemExit)
576
+ end
577
+
578
+ def raise_conflict(options = {})
579
+ options = {:state => "no_code"}.merge(options)
580
+ exception = RestClient::Conflict.new
581
+ exception.stub(:response).and_return(options.to_json)
582
+ @client.stub(:start_cloud).and_raise(exception)
583
+ end
584
+ end
585
+ end
586
+
587
+ describe "#stop" do
588
+ before do
589
+ @user = Shelly::User.new
590
+ @client.stub(:token).and_return("abc")
591
+ FileUtils.mkdir_p("/projects/foo")
592
+ Dir.chdir("/projects/foo")
593
+ File.open("Cloudfile", 'w') {|f| f.write("foo-production:\n") }
594
+ Shelly::User.stub(:new).and_return(@user)
595
+ @client.stub(:apps).and_return([{"code_name" => "foo-production"}, {"code_name" => "foo-staging"}])
596
+ @app = Shelly::App.new
597
+ Shelly::App.stub(:new).and_return(@app)
598
+ end
599
+
600
+ it "should exit if there is no Cloudfile" do
601
+ File.delete("Cloudfile")
602
+ $stdout.should_receive(:puts).with("\e[31mNo Cloudfile found\e[0m")
603
+ lambda {
604
+ @main.stop
605
+ }.should raise_error(SystemExit)
606
+ end
607
+
608
+ it "should exit if user doesn't have access to clouds in Cloudfile" do
609
+ response = {"message" => "Cloud foo-production not found"}
610
+ exception = Shelly::Client::APIError.new(response.to_json)
611
+ @client.stub(:stop_cloud).and_raise(exception)
612
+ $stdout.should_receive(:puts).with(red "You have no access to 'foo-production' cloud defined in Cloudfile")
613
+ lambda { @main.stop }.should raise_error(SystemExit)
614
+ end
615
+
616
+ it "should exit if user is not logged in" do
617
+ response = {"message" => "Unauthorized"}
618
+ exception = Shelly::Client::APIError.new(response.to_json)
619
+ @client.stub(:token).and_raise(exception)
620
+ $stdout.should_receive(:puts).with(red "You are not logged in. To log in use:")
621
+ $stdout.should_receive(:puts).with(" shelly login")
622
+ lambda { @main.stop }.should raise_error(SystemExit)
623
+ end
624
+
625
+ context "single cloud in Cloudfile" do
626
+ it "should start the cloud" do
627
+ @client.stub(:stop_cloud)
628
+ $stdout.should_receive(:puts).with("Cloud 'foo-production' stopped")
629
+ @main.stop
630
+ end
631
+ end
632
+
633
+ context "multiple clouds in Cloudfile" do
634
+ before do
635
+ File.open("Cloudfile", 'w') {|f| f.write("foo-staging:\nfoo-production:\n") }
636
+ end
637
+
638
+ it "should show information to start specific cloud and exit" do
639
+ $stdout.should_receive(:puts).with("You have multiple clouds in Cloudfile. Select which to stop using:")
640
+ $stdout.should_receive(:puts).with(" shelly stop foo-production")
641
+ $stdout.should_receive(:puts).with("Available clouds:")
642
+ $stdout.should_receive(:puts).with(" * foo-production")
643
+ $stdout.should_receive(:puts).with(" * foo-staging")
644
+ lambda { @main.stop }.should raise_error(SystemExit)
645
+ end
646
+
647
+ it "should fetch from command line which cloud to start" do
648
+ @client.should_receive(:stop_cloud).with("foo-staging")
649
+ $stdout.should_receive(:puts).with("Cloud 'foo-staging' stopped")
650
+ @main.stop("foo-staging")
651
+ end
652
+ end
443
653
  end
444
654
 
445
655
  describe "#ips" do
@@ -466,17 +676,16 @@ OUT
466
676
 
467
677
  context "on success" do
468
678
  it "should display mail and web server ip's" do
469
- @client.should_receive(:apps).and_return([{"code_name" => "foo-production"},{"code_name" => "foo-staging"}])
470
- @client.stub(:apps_ips).and_return(response)
471
- $stdout.should_receive(:puts).with("Cloud foo-production:")
472
- $stdout.should_receive(:puts).with("Web server IP : 22.22.22.22")
473
- $stdout.should_receive(:puts).with("Mail server IP: 11.11.11.11")
679
+ @client.stub(:app_ips).and_return(response)
680
+ $stdout.should_receive(:puts).with("\e[32mCloud foo-production:\e[0m")
681
+ $stdout.should_receive(:puts).with(" Web server IP: 22.22.22.22")
682
+ $stdout.should_receive(:puts).with(" Mail server IP: 11.11.11.11")
474
683
  @main.ip
475
684
  end
476
685
  end
477
686
 
478
687
  def response
479
- [{'code_name' => 'foo-production', 'mail_server_ip' => '11.11.11.11', 'web_server_ip' => '22.22.22.22'}]
688
+ {'mail_server_ip' => '11.11.11.11', 'web_server_ip' => '22.22.22.22'}
480
689
  end
481
690
 
482
691
  context "on failure" do
@@ -487,12 +696,82 @@ OUT
487
696
  end
488
697
 
489
698
  it "should raise an error if user does not have access to cloud" do
490
- @client.should_receive(:apps).and_return([{"code_name" => "foo-production"}])
491
- @client.stub(:apps_users).and_return(response)
492
- $stdout.should_receive(:puts).with("\e[31mYou have no access to 'foo-staging' cloud defined in Cloudfile\e[0m")
493
- lambda { @main.ip }.should raise_error(SystemExit)
699
+ response = {"message" => "Cloud foo-staging not found"}
700
+ exception = Shelly::Client::APIError.new(response.to_json)
701
+ @client.stub(:app_ips).and_raise(exception)
702
+ $stdout.should_receive(:puts).with(red "You have no access to 'foo-staging' cloud defined in Cloudfile")
703
+ @main.ip
494
704
  end
495
705
  end
496
706
  end
497
- end
498
707
 
708
+ describe "#delete" do
709
+ before do
710
+ Shelly::App.stub(:inside_git_repository?).and_return(true)
711
+ @user = Shelly::User.new
712
+ @app = Shelly::App.new
713
+ @client.stub(:token).and_return("abc")
714
+ @app.stub(:delete)
715
+ Shelly::User.stub(:new).and_return(@user)
716
+ Shelly::App.stub(:new).and_return(@app)
717
+ end
718
+
719
+ context "when code_name is not nil" do
720
+ it "should ask about delete application parts" do
721
+ $stdout.should_receive(:puts).with("You are about to delete application: foo-bar.")
722
+ $stdout.should_receive(:puts).with("Press Control-C at any moment to cancel.")
723
+ $stdout.should_receive(:puts).with("Please confirm each question by typing yes and pressing Enter.")
724
+ $stdout.should_receive(:puts).with("\n")
725
+ $stdout.should_receive(:print).with("I want to delete all files stored on Shelly Cloud (yes/no): ")
726
+ $stdout.should_receive(:print).with("I want to delete all database data stored on Shelly Cloud (yes/no): ")
727
+ $stdout.should_receive(:print).with("I want to delete the application (yes/no): ")
728
+ $stdout.should_receive(:puts).with("\n")
729
+ $stdout.should_receive(:puts).with("Scheduling application delete - done")
730
+ $stdout.should_receive(:puts).with("Removing git remote - done")
731
+ fake_stdin(["yes", "yes", "yes"]) do
732
+ @main.delete("foo-bar")
733
+ end
734
+ end
735
+
736
+ it "should return exit 1 when user doesn't type 'yes'" do
737
+ lambda{
738
+ fake_stdin(["yes", "yes", "no"]) do
739
+ @main.delete("foo-bar")
740
+ end
741
+ }.should raise_error(SystemExit)
742
+ end
743
+ end
744
+
745
+ context "when git repository doesn't exist" do
746
+ it "should say that Git remote missing" do
747
+ Shelly::App.stub(:inside_git_repository?).and_return(false)
748
+ $stdout.should_receive(:puts).with("Missing git remote")
749
+ fake_stdin(["yes", "yes", "yes"]) do
750
+ @main.delete("foo-bar")
751
+ end
752
+ end
753
+ end
754
+
755
+ context "when application with CODE-NAME doesn't exist" do
756
+ it "should raise Client::APIError" do
757
+ response = {:message => "Application not found"}
758
+ exception = Shelly::Client::APIError.new(response.to_json)
759
+ @app.stub(:delete).and_raise(exception)
760
+ $stdout.should_receive(:puts).with("\e[31mApplication not found\e[0m")
761
+ lambda{
762
+ fake_stdin(["yes", "yes", "yes"]) do
763
+ @main.delete("foo-bar")
764
+ end
765
+ }.should raise_error(SystemExit)
766
+ end
767
+ end
768
+
769
+ context "when code_name is nil" do
770
+ it "should display error when no code_name" do
771
+ $stdout.should_receive(:puts).with("\e[31mMissing CODE-NAME\e[0m")
772
+ $stdout.should_receive(:puts).with("\e[31mUse: shelly delete CODE-NAME\e[0m")
773
+ lambda{ @main.delete }.should raise_error(SystemExit)
774
+ end
775
+ end
776
+ end
777
+ end