shelly 0.0.30 → 0.0.31
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 +26 -6
- data/lib/shelly/cli/config.rb +47 -0
- data/lib/shelly/cli/deploys.rb +43 -2
- data/lib/shelly/cli/main.rb +24 -0
- data/lib/shelly/client.rb +13 -1
- data/lib/shelly/cloudfile.rb +1 -0
- data/lib/shelly/version.rb +1 -1
- data/spec/shelly/app_spec.rb +36 -4
- data/spec/shelly/cli/config_spec.rb +55 -0
- data/spec/shelly/cli/deploys_spec.rb +78 -5
- data/spec/shelly/cli/main_spec.rb +108 -0
- data/spec/shelly/client_spec.rb +28 -2
- metadata +173 -115
data/lib/shelly/app.rb
CHANGED
@@ -50,16 +50,24 @@ module Shelly
|
|
50
50
|
File.open(cloudfile_path, "a+") { |f| f << content }
|
51
51
|
end
|
52
52
|
|
53
|
-
def
|
54
|
-
shelly.
|
53
|
+
def deploy_logs
|
54
|
+
shelly.deploy_logs(code_name)
|
55
|
+
end
|
56
|
+
|
57
|
+
def deploy_log(log)
|
58
|
+
shelly.deploy_log(code_name, log)
|
59
|
+
end
|
60
|
+
|
61
|
+
def application_logs
|
62
|
+
shelly.application_logs(self.code_name)
|
55
63
|
end
|
56
64
|
|
57
65
|
def start
|
58
|
-
shelly.start_cloud(
|
66
|
+
shelly.start_cloud(code_name)
|
59
67
|
end
|
60
68
|
|
61
69
|
def stop
|
62
|
-
shelly.stop_cloud(
|
70
|
+
shelly.stop_cloud(code_name)
|
63
71
|
end
|
64
72
|
|
65
73
|
def cloudfile_path
|
@@ -71,11 +79,23 @@ module Shelly
|
|
71
79
|
end
|
72
80
|
|
73
81
|
def ips
|
74
|
-
shelly.app_ips(
|
82
|
+
shelly.app_ips(code_name)
|
75
83
|
end
|
76
84
|
|
77
85
|
def users
|
78
|
-
shelly.app_users(
|
86
|
+
shelly.app_users(code_name)
|
87
|
+
end
|
88
|
+
|
89
|
+
def configs
|
90
|
+
@configs ||= shelly.app_configs(code_name)
|
91
|
+
end
|
92
|
+
|
93
|
+
def user_configs
|
94
|
+
configs.find_all { |config| config["created_by_user"] }
|
95
|
+
end
|
96
|
+
|
97
|
+
def shelly_generated_configs
|
98
|
+
configs.find_all { |config| config["created_by_user"] == false }
|
79
99
|
end
|
80
100
|
|
81
101
|
def open_billing_page
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "shelly/cli/command"
|
2
|
+
|
3
|
+
module Shelly
|
4
|
+
module CLI
|
5
|
+
class Config < Command
|
6
|
+
include Thor::Actions
|
7
|
+
include Helpers
|
8
|
+
|
9
|
+
desc "list", "List configuration files"
|
10
|
+
def list
|
11
|
+
logged_in?
|
12
|
+
say_error "No Cloudfile found" unless Cloudfile.present?
|
13
|
+
cloudfile = Cloudfile.new
|
14
|
+
cloudfile.clouds.each do |cloud|
|
15
|
+
@app = App.new(cloud)
|
16
|
+
begin
|
17
|
+
configs = @app.configs
|
18
|
+
unless configs.empty?
|
19
|
+
say "Configuration files for #{cloud}", :green
|
20
|
+
user_configs = @app.user_configs
|
21
|
+
unless user_configs.empty?
|
22
|
+
say "Custom configuration files:"
|
23
|
+
user_configs.each { |config| say " * #{config["path"]}" }
|
24
|
+
else
|
25
|
+
say "You have no custom configuration files."
|
26
|
+
end
|
27
|
+
shelly_configs = @app.shelly_generated_configs
|
28
|
+
unless shelly_configs.empty?
|
29
|
+
say "Following files are created by Shelly Cloud:"
|
30
|
+
shelly_configs.each { |config| say " * #{config["path"]}" }
|
31
|
+
end
|
32
|
+
else
|
33
|
+
say "Cloud #{cloud} has no configuration files"
|
34
|
+
end
|
35
|
+
rescue Client::APIError => e
|
36
|
+
if e.unauthorized?
|
37
|
+
say_error "You have no access to '#{@app.code_name}' cloud defined in Cloudfile"
|
38
|
+
else
|
39
|
+
say_error e.message
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/shelly/cli/deploys.rb
CHANGED
@@ -11,8 +11,8 @@ module Shelly
|
|
11
11
|
def list(cloud = nil)
|
12
12
|
logged_in?
|
13
13
|
say_error "No Cloudfile found" unless Cloudfile.present?
|
14
|
-
multiple_clouds(cloud, "
|
15
|
-
logs = @app.
|
14
|
+
multiple_clouds(cloud, "deploys list", "Select cloud to view deploy logs using:")
|
15
|
+
logs = @app.deploy_logs
|
16
16
|
unless logs.empty?
|
17
17
|
say "Available deploy logs", :green
|
18
18
|
logs.each do |log|
|
@@ -29,6 +29,47 @@ module Shelly
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
+
desc "show LOG", "Show specific deploy log"
|
33
|
+
def show(log = nil, cloud = nil)
|
34
|
+
say_error "No Cloudfile found" unless Cloudfile.present?
|
35
|
+
specify_log(log)
|
36
|
+
multiple_clouds(cloud, "deploys show #{log}", "Select log and cloud to view deploy logs using:")
|
37
|
+
content = @app.deploy_log(log)
|
38
|
+
say "Log for deploy done on #{content["created_at"]}", :green
|
39
|
+
if content["bundle_install"]
|
40
|
+
say("Starting bundle install", :green); say(content["bundle_install"])
|
41
|
+
end
|
42
|
+
if content["whenever"]
|
43
|
+
say("Starting whenever", :green); say(content["whenever"])
|
44
|
+
end
|
45
|
+
if content["callbacks"]
|
46
|
+
say("Starting callbacks", :green); say(content["callbacks"])
|
47
|
+
end
|
48
|
+
if content["delayed_job"]
|
49
|
+
say("Starting delayed job", :green); say(content["delayed_job"])
|
50
|
+
end
|
51
|
+
if content["thin_restart"]
|
52
|
+
say("Starting thin", :green); say(content["thin_restart"])
|
53
|
+
end
|
54
|
+
rescue Client::APIError => e
|
55
|
+
if e.unauthorized?
|
56
|
+
say_error "You have no access to '#{@app.code_name}' cloud defined in Cloudfile"
|
57
|
+
elsif e.message == "Log not found"
|
58
|
+
say_error "Log not found, list all deploy logs using:", :with_exit => false
|
59
|
+
say " shelly deploys list #{cloud}"
|
60
|
+
exit 1
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
no_tasks do
|
65
|
+
def specify_log(log)
|
66
|
+
unless log
|
67
|
+
say_error "Specify log by passing date value or to see last log use:", :with_exit => false
|
68
|
+
say " shelly deploys show last"
|
69
|
+
exit 1
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
32
73
|
end
|
33
74
|
end
|
34
75
|
end
|
data/lib/shelly/cli/main.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "shelly/cli/command"
|
2
2
|
require "shelly/cli/user"
|
3
3
|
require "shelly/cli/deploys"
|
4
|
+
require "shelly/cli/config"
|
4
5
|
|
5
6
|
module Shelly
|
6
7
|
module CLI
|
@@ -9,6 +10,7 @@ module Shelly
|
|
9
10
|
include Helpers
|
10
11
|
register(User, "user", "user <command>", "Manages users using this cloud")
|
11
12
|
register(Deploys, "deploys", "deploys <command>", "View cloud deploy logs")
|
13
|
+
register(Config, "config", "config <command>", "Manages cloud configuration files")
|
12
14
|
check_unknown_options!
|
13
15
|
|
14
16
|
map %w(-v --version) => :version
|
@@ -234,6 +236,28 @@ module Shelly
|
|
234
236
|
say_error e.message
|
235
237
|
end
|
236
238
|
|
239
|
+
desc "logs", "Show latest application logs from each instance"
|
240
|
+
method_option :cloud, :type => :string, :aliases => "-c",
|
241
|
+
:desc => "Specify which cloud to show logs for"
|
242
|
+
def logs
|
243
|
+
cloud = options[:cloud]
|
244
|
+
logged_in?
|
245
|
+
say_error "No Cloudfile found" unless Cloudfile.present?
|
246
|
+
multiple_clouds(cloud, "logs --cloud", "Select which to show logs for using:")
|
247
|
+
begin
|
248
|
+
logs = @app.application_logs
|
249
|
+
say "Cloud #{@app.code_name}:", :green
|
250
|
+
logs.each_with_index do |log, i|
|
251
|
+
say "Instance #{i+1}:"
|
252
|
+
print_wrapped log, :ident => 2
|
253
|
+
end
|
254
|
+
rescue Client::APIError => e
|
255
|
+
if e.unauthorized?
|
256
|
+
say_error "You have no access to cloud '#{cloud || @app.code_name}'"
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
237
261
|
# FIXME: move to helpers
|
238
262
|
no_tasks do
|
239
263
|
def check_options(options)
|
data/lib/shelly/client.rb
CHANGED
@@ -58,6 +58,10 @@ module Shelly
|
|
58
58
|
get("/token")
|
59
59
|
end
|
60
60
|
|
61
|
+
def app_configs(cloud)
|
62
|
+
get("/apps/#{cloud}/configs")
|
63
|
+
end
|
64
|
+
|
61
65
|
def send_invitation(cloud, email)
|
62
66
|
post("/apps/#{cloud}/collaborations", :email => email)
|
63
67
|
end
|
@@ -86,10 +90,18 @@ module Shelly
|
|
86
90
|
get("/apps")
|
87
91
|
end
|
88
92
|
|
89
|
-
def
|
93
|
+
def deploy_logs(cloud)
|
90
94
|
get("/apps/#{cloud}/deploys")
|
91
95
|
end
|
92
96
|
|
97
|
+
def deploy_log(cloud, log)
|
98
|
+
get("/apps/#{cloud}/deploys/#{log}")
|
99
|
+
end
|
100
|
+
|
101
|
+
def application_logs(cloud)
|
102
|
+
get("/apps/#{cloud}/logs")
|
103
|
+
end
|
104
|
+
|
93
105
|
def ssh_key_available?(ssh_key)
|
94
106
|
get("/users/new", :ssh_key => ssh_key)
|
95
107
|
end
|
data/lib/shelly/cloudfile.rb
CHANGED
data/lib/shelly/version.rb
CHANGED
data/spec/shelly/app_spec.rb
CHANGED
@@ -48,6 +48,23 @@ describe Shelly::App do
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
+
describe "#configs" do
|
52
|
+
it "should get configs from client" do
|
53
|
+
@client.should_receive(:app_configs).with("foo-staging")
|
54
|
+
@app.configs
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should return only user config files" do
|
58
|
+
@client.should_receive(:app_configs).with("foo-staging").and_return([])
|
59
|
+
@app.user_configs
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should return only shelly genereted config files" do
|
63
|
+
@client.should_receive(:app_configs).with("foo-staging").and_return([])
|
64
|
+
@app.shelly_generated_configs
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
51
68
|
describe "#generate_cloudfile" do
|
52
69
|
it "should return generated cloudfile" do
|
53
70
|
user = mock(:email => "bob@example.com")
|
@@ -128,10 +145,25 @@ config
|
|
128
145
|
end
|
129
146
|
end
|
130
147
|
|
131
|
-
describe "#
|
132
|
-
it "should list
|
133
|
-
@client.should_receive(:
|
134
|
-
@app.
|
148
|
+
describe "#deploy_logs" do
|
149
|
+
it "should list deploy_logs" do
|
150
|
+
@client.should_receive(:deploy_logs).with("foo-staging")
|
151
|
+
@app.deploy_logs
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "#application_logs" do
|
156
|
+
it "should list application_logs" do
|
157
|
+
@client.should_receive(:application_logs).with("foo-staging").
|
158
|
+
and_return({"logs" => ["log1", "log2"]})
|
159
|
+
@app.application_logs
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
describe "#deploy_log" do
|
164
|
+
it "should show log" do
|
165
|
+
@client.should_receive(:deploy_log).with("foo-staging", "2011-11-29-11-50-16")
|
166
|
+
@app.deploy_log("2011-11-29-11-50-16")
|
135
167
|
end
|
136
168
|
end
|
137
169
|
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "shelly/cli/config"
|
3
|
+
|
4
|
+
describe Shelly::CLI::Config do
|
5
|
+
|
6
|
+
before do
|
7
|
+
FileUtils.stub(:chmod)
|
8
|
+
@config = Shelly::CLI::Config.new
|
9
|
+
@client = mock
|
10
|
+
Shelly::Client.stub(:new).and_return(@client)
|
11
|
+
$stdout.stub(:puts)
|
12
|
+
$stdout.stub(:print)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#list" do
|
16
|
+
before do
|
17
|
+
FileUtils.mkdir_p("/projects/foo")
|
18
|
+
Dir.chdir("/projects/foo")
|
19
|
+
File.open("Cloudfile", 'w') {|f| f.write("foo-staging:\nfoo-production:\n") }
|
20
|
+
@client.stub(:token).and_return("abc")
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should exit with message if there is no Cloudfile" do
|
24
|
+
File.delete("Cloudfile")
|
25
|
+
$stdout.should_receive(:puts).with(red "No Cloudfile found")
|
26
|
+
lambda {
|
27
|
+
@config.list
|
28
|
+
}.should raise_error(SystemExit)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should exit if user doesn't have access to cloud in Cloudfile" do
|
32
|
+
response = {"message" => "Cloud foo-staging not found"}
|
33
|
+
exception = Shelly::Client::APIError.new(response.to_json)
|
34
|
+
@client.stub(:app_configs).and_raise(exception)
|
35
|
+
$stdout.should_receive(:puts).with(red "You have no access to 'foo-production' cloud defined in Cloudfile")
|
36
|
+
lambda { @config.list }.should raise_error(SystemExit)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should list available configuration files for clouds" do
|
40
|
+
@client.should_receive(:app_configs).with("foo-staging").and_return([{"created_by_user" => true, "path" => "config/settings.yml"}])
|
41
|
+
@client.should_receive(:app_configs).with("foo-production").and_return([{"created_by_user" => false, "path" => "config/app.yml"}])
|
42
|
+
$stdout.should_receive(:puts).with(green "Configuration files for foo-production")
|
43
|
+
$stdout.should_receive(:puts).with("You have no custom configuration files.")
|
44
|
+
$stdout.should_receive(:puts).with("Following files are created by Shelly Cloud:")
|
45
|
+
$stdout.should_receive(:puts).with(" * config/app.yml")
|
46
|
+
$stdout.should_receive(:puts).with(green "Configuration files for foo-staging")
|
47
|
+
$stdout.should_receive(:puts).with("Custom configuration files:")
|
48
|
+
$stdout.should_receive(:puts).with(" * config/settings.yml")
|
49
|
+
|
50
|
+
@config.list
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -30,7 +30,7 @@ describe Shelly::CLI::Deploys do
|
|
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
32
|
exception = Shelly::Client::APIError.new(response.to_json)
|
33
|
-
@client.stub(:
|
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)
|
36
36
|
end
|
@@ -42,7 +42,7 @@ describe Shelly::CLI::Deploys do
|
|
42
42
|
|
43
43
|
it "should show information to select specific cloud and exit" do
|
44
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
|
45
|
+
$stdout.should_receive(:puts).with(" shelly deploys list foo-production")
|
46
46
|
$stdout.should_receive(:puts).with("Available clouds:")
|
47
47
|
$stdout.should_receive(:puts).with(" * foo-production")
|
48
48
|
$stdout.should_receive(:puts).with(" * foo-staging")
|
@@ -50,7 +50,7 @@ describe Shelly::CLI::Deploys do
|
|
50
50
|
end
|
51
51
|
|
52
52
|
it "should take cloud from command line for which to show logs" do
|
53
|
-
@client.should_receive(:
|
53
|
+
@client.should_receive(:deploy_logs).with("foo-staging").and_return([{"failed" => false, "created_at" => "2011-12-12-14-14-59"}])
|
54
54
|
$stdout.should_receive(:puts).with(green "Available deploy logs")
|
55
55
|
$stdout.should_receive(:puts).with(" * 2011-12-12-14-14-59")
|
56
56
|
@deploys.list("foo-staging")
|
@@ -59,15 +59,88 @@ describe Shelly::CLI::Deploys do
|
|
59
59
|
|
60
60
|
context "single cloud" do
|
61
61
|
it "should display available logs" do
|
62
|
-
@client.should_receive(:
|
62
|
+
@client.should_receive(:deploy_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
63
|
$stdout.should_receive(:puts).with(green "Available deploy logs")
|
64
64
|
$stdout.should_receive(:puts).with(" * 2011-12-12-14-14-59")
|
65
65
|
$stdout.should_receive(:puts).with(" * 2011-12-12-15-14-59 (failed)")
|
66
66
|
@deploys.list
|
67
67
|
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#show" do
|
72
|
+
before do
|
73
|
+
FileUtils.mkdir_p("/projects/foo")
|
74
|
+
Dir.chdir("/projects/foo")
|
75
|
+
File.open("Cloudfile", 'w') {|f| f.write("foo-staging:\n") }
|
76
|
+
@client.stub(:token).and_return("abc")
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should exit with message if there is no Cloudfile" do
|
80
|
+
File.delete("Cloudfile")
|
81
|
+
$stdout.should_receive(:puts).with("\e[31mNo Cloudfile found\e[0m")
|
82
|
+
lambda {
|
83
|
+
@deploys.show
|
84
|
+
}.should raise_error(SystemExit)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should exit if user doesn't have access to cloud in Cloudfile" do
|
88
|
+
response = {"message" => "Cloud foo-staging not found"}
|
89
|
+
exception = Shelly::Client::APIError.new(response.to_json)
|
90
|
+
@client.stub(:deploy_log).and_raise(exception)
|
91
|
+
$stdout.should_receive(:puts).with(red "You have no access to 'foo-staging' cloud defined in Cloudfile")
|
92
|
+
lambda { @deploys.show("last") }.should raise_error(SystemExit)
|
93
|
+
end
|
94
|
+
|
95
|
+
context "multiple clouds" do
|
96
|
+
before do
|
97
|
+
File.open("Cloudfile", 'w') {|f| f.write("foo-staging:\nfoo-production:\n") }
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should show information to select specific cloud and exit" do
|
101
|
+
$stdout.should_receive(:puts).with("You have multiple clouds in Cloudfile. Select log and cloud to view deploy logs using:")
|
102
|
+
$stdout.should_receive(:puts).with(" shelly deploys show last foo-production")
|
103
|
+
$stdout.should_receive(:puts).with("Available clouds:")
|
104
|
+
$stdout.should_receive(:puts).with(" * foo-production")
|
105
|
+
$stdout.should_receive(:puts).with(" * foo-staging")
|
106
|
+
lambda { @deploys.show("last") }.should raise_error(SystemExit)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should render the logs" do
|
110
|
+
@client.should_receive(:deploy_log).with("foo-staging", "last").and_return(response)
|
111
|
+
expected_output
|
112
|
+
@deploys.show("last", "foo-staging")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context "single cloud" do
|
117
|
+
it "should render logs without passing cloud" do
|
118
|
+
@client.should_receive(:deploy_log).with("foo-staging", "last").and_return(response)
|
119
|
+
expected_output
|
120
|
+
@deploys.show("last")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def expected_output
|
125
|
+
$stdout.should_receive(:puts).with(green "Log for deploy done on 2011-12-12 at 14:14:59")
|
126
|
+
$stdout.should_receive(:puts).with(green "Starting bundle install")
|
127
|
+
$stdout.should_receive(:puts).with("Installing gems")
|
128
|
+
$stdout.should_receive(:puts).with(green "Starting whenever")
|
129
|
+
$stdout.should_receive(:puts).with("Looking up schedule.rb")
|
130
|
+
$stdout.should_receive(:puts).with(green "Starting callbacks")
|
131
|
+
$stdout.should_receive(:puts).with("rake db:migrate")
|
132
|
+
$stdout.should_receive(:puts).with(green "Starting delayed job")
|
133
|
+
$stdout.should_receive(:puts).with("delayed jobs")
|
134
|
+
$stdout.should_receive(:puts).with(green "Starting thin")
|
135
|
+
$stdout.should_receive(:puts).with("thins up and running")
|
136
|
+
end
|
68
137
|
|
138
|
+
def response
|
139
|
+
{"created_at" => "2011-12-12 at 14:14:59", "bundle_install" => "Installing gems",
|
140
|
+
"whenever" => "Looking up schedule.rb", "thin_restart" => "thins up and running",
|
141
|
+
"delayed_job" => "delayed jobs", "callbacks" => "rake db:migrate"}
|
69
142
|
end
|
70
143
|
|
71
144
|
end
|
72
145
|
|
73
|
-
end
|
146
|
+
end
|
@@ -24,12 +24,14 @@ describe Shelly::CLI::Main do
|
|
24
24
|
expected = <<-OUT
|
25
25
|
Tasks:
|
26
26
|
shelly add # Adds new cloud to Shelly Cloud
|
27
|
+
shelly config <command> # Manages cloud configuration files
|
27
28
|
shelly delete CODE-NAME # Delete cloud from Shelly Cloud
|
28
29
|
shelly deploys <command> # View cloud deploy logs
|
29
30
|
shelly help [TASK] # Describe available tasks or one specific task
|
30
31
|
shelly ip # Lists clouds IP's
|
31
32
|
shelly list # Lists all your clouds
|
32
33
|
shelly login [EMAIL] # Logs user in to Shelly Cloud
|
34
|
+
shelly logs # Show latest application logs from each instance
|
33
35
|
shelly register [EMAIL] # Registers new user account on Shelly Cloud
|
34
36
|
shelly start [CODE-NAME] # Starts specific cloud
|
35
37
|
shelly stop [CODE-NAME] # Stops specific cloud
|
@@ -42,6 +44,21 @@ OUT
|
|
42
44
|
out = IO.popen("bin/shelly").read.strip
|
43
45
|
out.should == expected.strip
|
44
46
|
end
|
47
|
+
|
48
|
+
it "should display options in help for logs" do
|
49
|
+
expected = <<-OUT
|
50
|
+
Usage:
|
51
|
+
shelly logs
|
52
|
+
|
53
|
+
Options:
|
54
|
+
-c, [--cloud=CLOUD] # Specify which cloud to show logs for
|
55
|
+
[--debug] # Show debug information
|
56
|
+
|
57
|
+
Show latest application logs from each instance
|
58
|
+
OUT
|
59
|
+
out = IO.popen("bin/shelly help logs").read.strip
|
60
|
+
out.should == expected.strip
|
61
|
+
end
|
45
62
|
end
|
46
63
|
|
47
64
|
describe "#register" do
|
@@ -775,4 +792,95 @@ OUT
|
|
775
792
|
end
|
776
793
|
end
|
777
794
|
end
|
795
|
+
|
796
|
+
describe "#logs" do
|
797
|
+
before do
|
798
|
+
@user = Shelly::User.new
|
799
|
+
@client.stub(:token).and_return("abc")
|
800
|
+
FileUtils.mkdir_p("/projects/foo")
|
801
|
+
Dir.chdir("/projects/foo")
|
802
|
+
File.open("Cloudfile", 'w') {|f| f.write("foo-production:\n") }
|
803
|
+
Shelly::User.stub(:new).and_return(@user)
|
804
|
+
@client.stub(:apps).and_return([{"code_name" => "foo-production"},
|
805
|
+
{"code_name" => "foo-staging"}])
|
806
|
+
@app = Shelly::App.new
|
807
|
+
Shelly::App.stub(:new).and_return(@app)
|
808
|
+
end
|
809
|
+
|
810
|
+
it "should exit if there is no Cloudfile" do
|
811
|
+
File.delete("Cloudfile")
|
812
|
+
$stdout.should_receive(:puts).with("\e[31mNo Cloudfile found\e[0m")
|
813
|
+
lambda {
|
814
|
+
@main.logs
|
815
|
+
}.should raise_error(SystemExit)
|
816
|
+
end
|
817
|
+
|
818
|
+
it "should exit if user doesn't have access to clouds in Cloudfile" do
|
819
|
+
response = {"message" => "Cloud foo-production not found"}
|
820
|
+
exception = Shelly::Client::APIError.new(response.to_json)
|
821
|
+
@client.stub(:application_logs).and_raise(exception)
|
822
|
+
$stdout.should_receive(:puts).
|
823
|
+
with(red "You have no access to cloud 'foo-production'")
|
824
|
+
lambda { @main.logs }.should raise_error(SystemExit)
|
825
|
+
end
|
826
|
+
|
827
|
+
it "should exit if user is not logged in" do
|
828
|
+
response = {"message" => "Unauthorized"}
|
829
|
+
exception = Shelly::Client::APIError.new(response.to_json)
|
830
|
+
@client.stub(:token).and_raise(exception)
|
831
|
+
$stdout.should_receive(:puts).
|
832
|
+
with(red "You are not logged in. To log in use:")
|
833
|
+
$stdout.should_receive(:puts).with(" shelly login")
|
834
|
+
lambda { @main.logs }.should raise_error(SystemExit)
|
835
|
+
end
|
836
|
+
|
837
|
+
context "single cloud in Cloudfile" do
|
838
|
+
it "should show logs for the cloud" do
|
839
|
+
@client.stub(:application_logs).and_return(["log1"])
|
840
|
+
$stdout.should_receive(:puts).with(green "Cloud foo-production:")
|
841
|
+
$stdout.should_receive(:puts).with("Instance 1:")
|
842
|
+
$stdout.should_receive(:puts).with(" log1")
|
843
|
+
@main.logs
|
844
|
+
end
|
845
|
+
end
|
846
|
+
|
847
|
+
context "multiple clouds in Cloudfile" do
|
848
|
+
before do
|
849
|
+
File.open("Cloudfile", 'w') {|f|
|
850
|
+
f.write("foo-staging:\nfoo-production:\n") }
|
851
|
+
end
|
852
|
+
|
853
|
+
it "should show information to print logs for specific cloud and exit" do
|
854
|
+
$stdout.should_receive(:puts).
|
855
|
+
with("You have multiple clouds in Cloudfile. Select which to show logs for using:")
|
856
|
+
$stdout.should_receive(:puts).with(" shelly logs --cloud foo-production")
|
857
|
+
$stdout.should_receive(:puts).with("Available clouds:")
|
858
|
+
$stdout.should_receive(:puts).with(" * foo-production")
|
859
|
+
$stdout.should_receive(:puts).with(" * foo-staging")
|
860
|
+
lambda { @main.logs }.should raise_error(SystemExit)
|
861
|
+
end
|
862
|
+
|
863
|
+
it "should fetch from command line which cloud to start" do
|
864
|
+
@client.should_receive(:application_logs).with("foo-staging").
|
865
|
+
and_return(["log1"])
|
866
|
+
$stdout.should_receive(:puts).with(green "Cloud foo-staging:")
|
867
|
+
$stdout.should_receive(:puts).with("Instance 1:")
|
868
|
+
$stdout.should_receive(:puts).with(" log1")
|
869
|
+
@main.options = {:cloud => "foo-staging"}
|
870
|
+
@main.logs
|
871
|
+
end
|
872
|
+
end
|
873
|
+
|
874
|
+
context "multiple instances" do
|
875
|
+
it "should show logs from each instance" do
|
876
|
+
@client.stub(:application_logs).and_return(["log1", "log2"])
|
877
|
+
$stdout.should_receive(:puts).with(green "Cloud foo-production:")
|
878
|
+
$stdout.should_receive(:puts).with("Instance 1:")
|
879
|
+
$stdout.should_receive(:puts).with(" log1")
|
880
|
+
$stdout.should_receive(:puts).with("Instance 2:")
|
881
|
+
$stdout.should_receive(:puts).with(" log2")
|
882
|
+
@main.logs
|
883
|
+
end
|
884
|
+
end
|
885
|
+
end
|
778
886
|
end
|
data/spec/shelly/client_spec.rb
CHANGED
@@ -100,17 +100,43 @@ describe Shelly::Client do
|
|
100
100
|
end
|
101
101
|
end
|
102
102
|
|
103
|
-
describe "#
|
103
|
+
describe "#deploy_logs" do
|
104
104
|
it "should send get request" do
|
105
105
|
time = Time.now
|
106
106
|
FakeWeb.register_uri(:get, @url + "/apps/staging-foo/deploys", :body => [{:failed => false, :created_at => time},
|
107
107
|
{:failed => true, :created_at => time+1}].to_json)
|
108
|
-
response = @client.
|
108
|
+
response = @client.deploy_logs("staging-foo")
|
109
109
|
response.should == [{"failed"=>false, "created_at"=>time.to_s},
|
110
110
|
{"failed"=>true, "created_at"=>(time+1).to_s}]
|
111
111
|
end
|
112
112
|
end
|
113
113
|
|
114
|
+
describe "#deploy_log" do
|
115
|
+
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)
|
117
|
+
response = @client.deploy_log("staging-foo", "2011-11-29-11-50-16")
|
118
|
+
response.should == {"content" => "Log"}
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "#app_configs" do
|
123
|
+
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)
|
125
|
+
response = @client.app_configs("staging-foo")
|
126
|
+
response.should == [{"created_by_user" => true, "path" => "config/app.yml"}]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "#application_logs" do
|
131
|
+
it "should send get request" do
|
132
|
+
time = Time.now
|
133
|
+
FakeWeb.register_uri(:get, @url + "/apps/staging-foo/logs",
|
134
|
+
:body => {:logs => ["application_log_1", "application_log_2"]}.to_json)
|
135
|
+
response = @client.application_logs("staging-foo")
|
136
|
+
response.should == {"logs" => ["application_log_1", "application_log_2"]}
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
114
140
|
describe "#create_app" do
|
115
141
|
it "should send post with app's attributes" do
|
116
142
|
@client.should_receive(:post).with("/apps", :app => {:code_name => "foo", :ruby_version => "1.9.2"})
|
metadata
CHANGED
@@ -1,156 +1,202 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: shelly
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 33
|
5
5
|
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 31
|
10
|
+
version: 0.0.31
|
6
11
|
platform: ruby
|
7
|
-
authors:
|
12
|
+
authors:
|
8
13
|
- Shelly Cloud team
|
9
14
|
autorequire:
|
10
15
|
bindir: bin
|
11
16
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
17
|
+
|
18
|
+
date: 2011-12-19 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
15
21
|
name: rspec
|
16
|
-
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
17
24
|
none: false
|
18
|
-
requirements:
|
19
|
-
- -
|
20
|
-
- !ruby/object:Gem::Version
|
21
|
-
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
22
32
|
type: :development
|
23
|
-
|
24
|
-
|
25
|
-
- !ruby/object:Gem::Dependency
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
26
35
|
name: rake
|
27
|
-
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
28
38
|
none: false
|
29
|
-
requirements:
|
30
|
-
- -
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
33
46
|
type: :development
|
34
|
-
|
35
|
-
|
36
|
-
- !ruby/object:Gem::Dependency
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
37
49
|
name: guard
|
38
|
-
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
39
52
|
none: false
|
40
|
-
requirements:
|
41
|
-
- -
|
42
|
-
- !ruby/object:Gem::Version
|
43
|
-
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
44
60
|
type: :development
|
45
|
-
|
46
|
-
|
47
|
-
- !ruby/object:Gem::Dependency
|
61
|
+
version_requirements: *id003
|
62
|
+
- !ruby/object:Gem::Dependency
|
48
63
|
name: guard-rspec
|
49
|
-
|
64
|
+
prerelease: false
|
65
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
50
66
|
none: false
|
51
|
-
requirements:
|
52
|
-
- -
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
55
74
|
type: :development
|
56
|
-
|
57
|
-
|
58
|
-
- !ruby/object:Gem::Dependency
|
75
|
+
version_requirements: *id004
|
76
|
+
- !ruby/object:Gem::Dependency
|
59
77
|
name: growl_notify
|
60
|
-
|
78
|
+
prerelease: false
|
79
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
61
80
|
none: false
|
62
|
-
requirements:
|
63
|
-
- -
|
64
|
-
- !ruby/object:Gem::Version
|
65
|
-
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
hash: 3
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
version: "0"
|
66
88
|
type: :development
|
67
|
-
|
68
|
-
|
69
|
-
- !ruby/object:Gem::Dependency
|
89
|
+
version_requirements: *id005
|
90
|
+
- !ruby/object:Gem::Dependency
|
70
91
|
name: rb-fsevent
|
71
|
-
|
92
|
+
prerelease: false
|
93
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
72
94
|
none: false
|
73
|
-
requirements:
|
74
|
-
- -
|
75
|
-
- !ruby/object:Gem::Version
|
76
|
-
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
hash: 3
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
version: "0"
|
77
102
|
type: :development
|
78
|
-
|
79
|
-
|
80
|
-
- !ruby/object:Gem::Dependency
|
103
|
+
version_requirements: *id006
|
104
|
+
- !ruby/object:Gem::Dependency
|
81
105
|
name: fakefs
|
82
|
-
|
106
|
+
prerelease: false
|
107
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
83
108
|
none: false
|
84
|
-
requirements:
|
85
|
-
- -
|
86
|
-
- !ruby/object:Gem::Version
|
87
|
-
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
hash: 3
|
113
|
+
segments:
|
114
|
+
- 0
|
115
|
+
version: "0"
|
88
116
|
type: :development
|
89
|
-
|
90
|
-
|
91
|
-
- !ruby/object:Gem::Dependency
|
117
|
+
version_requirements: *id007
|
118
|
+
- !ruby/object:Gem::Dependency
|
92
119
|
name: fakeweb
|
93
|
-
|
120
|
+
prerelease: false
|
121
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
94
122
|
none: false
|
95
|
-
requirements:
|
96
|
-
- -
|
97
|
-
- !ruby/object:Gem::Version
|
98
|
-
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
hash: 3
|
127
|
+
segments:
|
128
|
+
- 0
|
129
|
+
version: "0"
|
99
130
|
type: :development
|
100
|
-
|
101
|
-
|
102
|
-
- !ruby/object:Gem::Dependency
|
131
|
+
version_requirements: *id008
|
132
|
+
- !ruby/object:Gem::Dependency
|
103
133
|
name: wijet-thor
|
104
|
-
|
134
|
+
prerelease: false
|
135
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
105
136
|
none: false
|
106
|
-
requirements:
|
137
|
+
requirements:
|
107
138
|
- - ~>
|
108
|
-
- !ruby/object:Gem::Version
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
hash: 41
|
141
|
+
segments:
|
142
|
+
- 0
|
143
|
+
- 14
|
144
|
+
- 7
|
109
145
|
version: 0.14.7
|
110
146
|
type: :runtime
|
111
|
-
|
112
|
-
|
113
|
-
- !ruby/object:Gem::Dependency
|
147
|
+
version_requirements: *id009
|
148
|
+
- !ruby/object:Gem::Dependency
|
114
149
|
name: rest-client
|
115
|
-
|
150
|
+
prerelease: false
|
151
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
116
152
|
none: false
|
117
|
-
requirements:
|
118
|
-
- -
|
119
|
-
- !ruby/object:Gem::Version
|
120
|
-
|
153
|
+
requirements:
|
154
|
+
- - ">="
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
hash: 3
|
157
|
+
segments:
|
158
|
+
- 0
|
159
|
+
version: "0"
|
121
160
|
type: :runtime
|
122
|
-
|
123
|
-
|
124
|
-
- !ruby/object:Gem::Dependency
|
161
|
+
version_requirements: *id010
|
162
|
+
- !ruby/object:Gem::Dependency
|
125
163
|
name: json
|
126
|
-
|
164
|
+
prerelease: false
|
165
|
+
requirement: &id011 !ruby/object:Gem::Requirement
|
127
166
|
none: false
|
128
|
-
requirements:
|
129
|
-
- -
|
130
|
-
- !ruby/object:Gem::Version
|
131
|
-
|
167
|
+
requirements:
|
168
|
+
- - ">="
|
169
|
+
- !ruby/object:Gem::Version
|
170
|
+
hash: 3
|
171
|
+
segments:
|
172
|
+
- 0
|
173
|
+
version: "0"
|
132
174
|
type: :runtime
|
133
|
-
|
134
|
-
|
135
|
-
- !ruby/object:Gem::Dependency
|
175
|
+
version_requirements: *id011
|
176
|
+
- !ruby/object:Gem::Dependency
|
136
177
|
name: wijet-launchy
|
137
|
-
|
178
|
+
prerelease: false
|
179
|
+
requirement: &id012 !ruby/object:Gem::Requirement
|
138
180
|
none: false
|
139
|
-
requirements:
|
140
|
-
- -
|
141
|
-
- !ruby/object:Gem::Version
|
142
|
-
|
181
|
+
requirements:
|
182
|
+
- - ">="
|
183
|
+
- !ruby/object:Gem::Version
|
184
|
+
hash: 3
|
185
|
+
segments:
|
186
|
+
- 0
|
187
|
+
version: "0"
|
143
188
|
type: :runtime
|
144
|
-
|
145
|
-
version_requirements: *70153945365700
|
189
|
+
version_requirements: *id012
|
146
190
|
description: Tool for managing applications and clouds at shellycloud.com
|
147
|
-
email:
|
191
|
+
email:
|
148
192
|
- support@shellycloud.com
|
149
|
-
executables:
|
193
|
+
executables:
|
150
194
|
- shelly
|
151
195
|
extensions: []
|
196
|
+
|
152
197
|
extra_rdoc_files: []
|
153
|
-
|
198
|
+
|
199
|
+
files:
|
154
200
|
- .gitignore
|
155
201
|
- .travis.yml
|
156
202
|
- Gemfile
|
@@ -163,6 +209,7 @@ files:
|
|
163
209
|
- lib/shelly.rb
|
164
210
|
- lib/shelly/app.rb
|
165
211
|
- lib/shelly/cli/command.rb
|
212
|
+
- lib/shelly/cli/config.rb
|
166
213
|
- lib/shelly/cli/deploys.rb
|
167
214
|
- lib/shelly/cli/main.rb
|
168
215
|
- lib/shelly/cli/runner.rb
|
@@ -181,6 +228,7 @@ files:
|
|
181
228
|
- spec/helpers.rb
|
182
229
|
- spec/input_faker.rb
|
183
230
|
- spec/shelly/app_spec.rb
|
231
|
+
- spec/shelly/cli/config_spec.rb
|
184
232
|
- spec/shelly/cli/deploys_spec.rb
|
185
233
|
- spec/shelly/cli/main_spec.rb
|
186
234
|
- spec/shelly/cli/runner_spec.rb
|
@@ -193,32 +241,42 @@ files:
|
|
193
241
|
- spec/thor/options_spec.rb
|
194
242
|
homepage: http://shellycloud.com
|
195
243
|
licenses: []
|
244
|
+
|
196
245
|
post_install_message:
|
197
246
|
rdoc_options: []
|
198
|
-
|
247
|
+
|
248
|
+
require_paths:
|
199
249
|
- lib
|
200
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
250
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
201
251
|
none: false
|
202
|
-
requirements:
|
203
|
-
- -
|
204
|
-
- !ruby/object:Gem::Version
|
205
|
-
|
206
|
-
|
252
|
+
requirements:
|
253
|
+
- - ">="
|
254
|
+
- !ruby/object:Gem::Version
|
255
|
+
hash: 3
|
256
|
+
segments:
|
257
|
+
- 0
|
258
|
+
version: "0"
|
259
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
207
260
|
none: false
|
208
|
-
requirements:
|
209
|
-
- -
|
210
|
-
- !ruby/object:Gem::Version
|
211
|
-
|
261
|
+
requirements:
|
262
|
+
- - ">="
|
263
|
+
- !ruby/object:Gem::Version
|
264
|
+
hash: 3
|
265
|
+
segments:
|
266
|
+
- 0
|
267
|
+
version: "0"
|
212
268
|
requirements: []
|
269
|
+
|
213
270
|
rubyforge_project: shelly
|
214
271
|
rubygems_version: 1.8.10
|
215
272
|
signing_key:
|
216
273
|
specification_version: 3
|
217
274
|
summary: Shelly Cloud command line tool
|
218
|
-
test_files:
|
275
|
+
test_files:
|
219
276
|
- spec/helpers.rb
|
220
277
|
- spec/input_faker.rb
|
221
278
|
- spec/shelly/app_spec.rb
|
279
|
+
- spec/shelly/cli/config_spec.rb
|
222
280
|
- spec/shelly/cli/deploys_spec.rb
|
223
281
|
- spec/shelly/cli/main_spec.rb
|
224
282
|
- spec/shelly/cli/runner_spec.rb
|