shelly 0.0.28 → 0.0.29

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.
@@ -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