shelly 0.0.28 → 0.0.29

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,11 +8,19 @@ module Shelly
8
8
  attr_accessor :code_name, :databases, :ruby_version, :environment,
9
9
  :git_url, :domains
10
10
 
11
+ def initialize(code_name = nil)
12
+ self.code_name = code_name
13
+ end
14
+
11
15
  def add_git_remote
12
16
  system("git remote rm production > /dev/null 2>&1")
13
17
  system("git remote add production #{git_url}")
14
18
  end
15
19
 
20
+ def remove_git_remote
21
+ system("git remote rm production > /dev/null 2>&1")
22
+ end
23
+
16
24
  def generate_cloudfile
17
25
  @email = current_user.email
18
26
  template = File.read(cloudfile_template_path)
@@ -33,11 +41,28 @@ module Shelly
33
41
  self.environment = response["environment"]
34
42
  end
35
43
 
44
+ def delete(code_name)
45
+ shelly.delete_app(code_name)
46
+ #response = shelly.delete_app(code_name)
47
+ end
48
+
36
49
  def create_cloudfile
37
50
  content = generate_cloudfile
38
51
  File.open(cloudfile_path, "a+") { |f| f << content }
39
52
  end
40
53
 
54
+ def logs
55
+ shelly.cloud_logs(self.code_name)
56
+ end
57
+
58
+ def start
59
+ shelly.start_cloud(self.code_name)
60
+ end
61
+
62
+ def stop
63
+ shelly.stop_cloud(self.code_name)
64
+ end
65
+
41
66
  def cloudfile_path
42
67
  File.join(Dir.pwd, "Cloudfile")
43
68
  end
@@ -46,8 +71,12 @@ module Shelly
46
71
  File.basename(Dir.pwd)
47
72
  end
48
73
 
49
- def users(apps)
50
- shelly.app_users(apps)
74
+ def ips
75
+ shelly.app_ips(self.code_name)
76
+ end
77
+
78
+ def users
79
+ shelly.app_users(self.code_name)
51
80
  end
52
81
 
53
82
  def open_billing_page
@@ -3,6 +3,7 @@ require "thor"
3
3
  require "thor/group"
4
4
  require "thor/options"
5
5
  require "thor/arguments"
6
+ require "thor/basic"
6
7
 
7
8
  module Shelly
8
9
  module CLI
@@ -0,0 +1,34 @@
1
+ require "shelly/cli/command"
2
+ require "time"
3
+
4
+ module Shelly
5
+ module CLI
6
+ class Deploys < Command
7
+ namespace :deploys
8
+ include Helpers
9
+
10
+ desc "list", "Lists deploy logs"
11
+ def list(cloud = nil)
12
+ logged_in?
13
+ say_error "No Cloudfile found" unless Cloudfile.present?
14
+ multiple_clouds(cloud, "deploy list", "Select cloud to view deploy logs using:")
15
+ logs = @app.logs
16
+ unless logs.empty?
17
+ say "Available deploy logs", :green
18
+ logs.each do |log|
19
+ log["failed"] ? say(" * #{log["created_at"]} (failed)") : say(" * #{log["created_at"]}")
20
+ end
21
+ else
22
+ say "No deploy logs available"
23
+ end
24
+ rescue Client::APIError => e
25
+ if e.unauthorized?
26
+ say_error "You have no access to '#{@app.code_name}' cloud defined in Cloudfile"
27
+ else
28
+ say_error e.message
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -1,5 +1,6 @@
1
1
  require "shelly/cli/command"
2
2
  require "shelly/cli/user"
3
+ require "shelly/cli/deploys"
3
4
 
4
5
  module Shelly
5
6
  module CLI
@@ -7,6 +8,7 @@ module Shelly
7
8
  include Thor::Actions
8
9
  include Helpers
9
10
  register(User, "user", "user <command>", "Manages users using this cloud")
11
+ register(Deploys, "deploys", "deploys <command>", "View cloud deploy logs")
10
12
  check_unknown_options!
11
13
 
12
14
  map %w(-v --version) => :version
@@ -17,11 +19,11 @@ module Shelly
17
19
 
18
20
  desc "register [EMAIL]", "Registers new user account on Shelly Cloud"
19
21
  def register(email = nil)
20
- user = Shelly::User.new
21
- user.ssh_key_registered?
22
+ user = Shelly::User.new
23
+ user.ssh_key_registered?
22
24
  say "Registering with email: #{email}" if email
23
- user.email = (email || ask_for_email)
24
- user.password = ask_for_password
25
+ user.email = (email || ask_for_email)
26
+ user.password = ask_for_password
25
27
  user.register
26
28
  if user.ssh_key_exists?
27
29
  say "Uploading your public SSH key from #{user.ssh_key_path}"
@@ -44,16 +46,19 @@ module Shelly
44
46
 
45
47
  desc "login [EMAIL]", "Logs user in to Shelly Cloud"
46
48
  def login(email = nil)
47
- user = Shelly::User.new(email || ask_for_email, ask_for_password(:with_confirmation => false))
49
+ user = Shelly::User.new
50
+ raise Errno::ENOENT, user.ssh_key_path unless user.ssh_key_exists?
51
+ user.email = email || ask_for_email
52
+ user.password = ask_for_password(:with_confirmation => false)
48
53
  user.login
49
54
  say "Login successful"
55
+ # FIXME: remove conflict boolean, move it to rescue block
50
56
  begin user.upload_ssh_key
51
- conflict = false
57
+ conflict = false
52
58
  rescue RestClient::Conflict
53
59
  conflict = true
54
60
  end
55
61
  say "Uploading your public SSH key" if conflict == false
56
- email = nil
57
62
  list
58
63
  rescue Client::APIError => e
59
64
  if e.validation?
@@ -65,6 +70,9 @@ module Shelly
65
70
  say_error "#{e.url}", :with_exit => false
66
71
  end
67
72
  exit 1
73
+ rescue Errno::ENOENT => e
74
+ say_error e, :with_exit => false
75
+ say_error "Use ssh-keygen to generate ssh key pair"
68
76
  end
69
77
 
70
78
  method_option "code-name", :type => :string, :aliases => "-c",
@@ -131,21 +139,100 @@ module Shelly
131
139
  def ip
132
140
  say_error "Must be run inside your project git repository" unless App.inside_git_repository?
133
141
  say_error "No Cloudfile found" unless Cloudfile.present?
134
- @cloudfile = check_clouds.first
135
- @cloudfile.fetch_ips.each do |server|
136
- say "Cloud #{server['code_name']}:"
137
- say "Web server IP : #{server['web_server_ip']}"
138
- say "Mail server IP: #{server['mail_server_ip']}"
142
+ @cloudfile = Cloudfile.new
143
+ @cloudfile.clouds.each do |cloud|
144
+ begin
145
+ @app = App.new(cloud)
146
+ say "Cloud #{cloud}:", :green
147
+ ips = @app.ips
148
+ print_wrapped "Web server IP: #{ips['web_server_ip']}", :ident => 2
149
+ print_wrapped "Mail server IP: #{ips['mail_server_ip']}", :ident => 2
150
+ rescue Client::APIError => e
151
+ if e.unauthorized?
152
+ say_error "You have no access to '#{cloud}' cloud defined in Cloudfile", :with_exit => false
153
+ else
154
+ say_error e.message, :with_exit => false
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ desc "start [CODE-NAME]", "Starts specific cloud"
161
+ def start(cloud = nil)
162
+ logged_in?
163
+ say_error "No Cloudfile found" unless Cloudfile.present?
164
+ multiple_clouds(cloud, "start", "Select which to start using:")
165
+ @app.start
166
+ say "Starting cloud #{@app.code_name}. Check status with:", :green
167
+ say " shelly list"
168
+ rescue RestClient::Conflict => e
169
+ response = JSON.parse(e.response)
170
+ case response['state']
171
+ when "running"
172
+ say_error "Not starting: cloud '#{@app.code_name}' is already running"
173
+ when "deploying", "configuring"
174
+ say_error "Not starting: cloud '#{@app.code_name}' is currently deploying"
175
+ when "no_code"
176
+ say_error "Not starting: no source code provided", :with_exit => false
177
+ say_error "Push source code using:", :with_exit => false
178
+ say " git push production master"
179
+ when "deploy_failed", "configuration_failed"
180
+ say_error "Not starting: deployment failed", :with_exit => false
181
+ say_error "Support has been notified", :with_exit => false
182
+ say_error "See #{response['link']} for reasons of failure"
183
+ when "no_billing"
184
+ say_error "Please fill in billing details to start foo-production. Opening browser.", :with_exit => false
185
+ @app.open_billing_page
139
186
  end
187
+ exit 1
140
188
  rescue Client::APIError => e
141
189
  if e.unauthorized?
142
- e.errors.each { |error| say_error error, :with_exit => false}
143
- exit 1
144
- else
145
- say_error e.message
190
+ say_error "You have no access to '#{@app.code_name}' cloud defined in Cloudfile"
146
191
  end
147
192
  end
148
193
 
194
+ desc "stop [CODE-NAME]", "Stops specific cloud"
195
+ def stop(cloud = nil)
196
+ logged_in?
197
+ say_error "No Cloudfile found" unless Cloudfile.present?
198
+ multiple_clouds(cloud, "stop", "Select which to stop using:")
199
+ @app.stop
200
+ say "Cloud '#{@app.code_name}' stopped"
201
+ rescue Client::APIError => e
202
+ if e.unauthorized?
203
+ say_error "You have no access to '#{@app.code_name}' cloud defined in Cloudfile"
204
+ end
205
+ end
206
+
207
+ desc "delete CODE-NAME", "Delete cloud from Shelly Cloud"
208
+ def delete(code_name = nil)
209
+ user = Shelly::User.new
210
+ user.token
211
+ if code_name.present?
212
+ app = Shelly::App.new
213
+ say "You are about to delete application: #{code_name}."
214
+ say "Press Control-C at any moment to cancel."
215
+ say "Please confirm each question by typing yes and pressing Enter."
216
+ say_new_line
217
+ ask_to_delete_files
218
+ ask_to_delete_database
219
+ ask_to_delete_application
220
+ app.delete(code_name)
221
+ say_new_line
222
+ say "Scheduling application delete - done"
223
+ if App.inside_git_repository?
224
+ app.remove_git_remote
225
+ say "Removing git remote - done"
226
+ else
227
+ say "Missing git remote"
228
+ end
229
+ else
230
+ say_error "Missing CODE-NAME", :with_exit => false
231
+ say_error "Use: shelly delete CODE-NAME"
232
+ end
233
+ rescue Client::APIError => e
234
+ say_error e.message
235
+ end
149
236
 
150
237
  # FIXME: move to helpers
151
238
  no_tasks do
@@ -224,4 +311,3 @@ module Shelly
224
311
  end
225
312
  end
226
313
  end
227
-
@@ -10,17 +10,19 @@ module Shelly
10
10
  def list
11
11
  say_error "Must be run inside your project git repository" unless App.inside_git_repository?
12
12
  say_error "No Cloudfile found" unless Cloudfile.present?
13
- @cloudfile = check_clouds.first
14
- @cloudfile.fetch_users.each do |cloud, users|
15
- say "Cloud #{cloud}:"
16
- users.each { |user| say " #{user}" }
17
- end
18
- rescue Client::APIError => e
19
- if e.unauthorized?
20
- e.errors.each { |error| say_error error, :with_exit => false}
21
- exit 1
22
- else
23
- say_error e.message
13
+ @cloudfile = Cloudfile.new
14
+ @cloudfile.clouds.each do |cloud|
15
+ begin
16
+ @app = App.new(cloud)
17
+ say "Cloud #{cloud}:"
18
+ @app.users.each { |user| say " #{user["email"]}" }
19
+ rescue Client::APIError => e
20
+ if e.unauthorized?
21
+ say_error "You have no access to '#{cloud}' cloud defined in Cloudfile", :with_exit => false
22
+ else
23
+ say_error e.message, :with_exit => false
24
+ end
25
+ end
24
26
  end
25
27
  end
26
28
 
@@ -28,7 +30,8 @@ module Shelly
28
30
  def add(email = nil)
29
31
  say_error "Must be run inside your project git repository" unless App.inside_git_repository?
30
32
  say_error "No Cloudfile found" unless Cloudfile.present?
31
- @cloudfile, @user = check_clouds
33
+ @cloudfile = Cloudfile.new
34
+ @user = Shelly::User.new
32
35
  user_email = email || ask_for_email({:guess_email => false})
33
36
  @cloudfile.clouds.each do |cloud|
34
37
  begin
@@ -36,9 +39,13 @@ module Shelly
36
39
  rescue Client::APIError => e
37
40
  if e.validation? && e.errors.include?(["email", "#{email} has already been taken"])
38
41
  say_error "User #{email} is already in the cloud #{cloud}", :with_exit => false
42
+ elsif e.unauthorized?
43
+ say_error "You have no access to '#{cloud}' cloud defined in Cloudfile", :with_exit => false
39
44
  elsif e.validation?
40
45
  e.each_error { |error| say_error error, :with_exit => false }
41
46
  exit 1
47
+ else
48
+ say_error e.message, :with_exit => false
42
49
  end
43
50
  else
44
51
  say "Sending invitation to #{user_email} to work on #{cloud}", :green
@@ -25,7 +25,7 @@ module Shelly
25
25
  end
26
26
 
27
27
  def unauthorized?
28
- message == "Unauthorized"
28
+ message == "Unauthorized" || message =~ /Cloud .+ not found/
29
29
  end
30
30
 
31
31
  def each_error
@@ -64,28 +64,40 @@ module Shelly
64
64
  post("/apps", :app => attributes)
65
65
  end
66
66
 
67
+ def delete_app(code_name)
68
+ delete("/apps/#{code_name}")
69
+ end
70
+
67
71
  def add_ssh_key(ssh_key)
68
72
  post("/ssh_key", :ssh_key => ssh_key)
69
73
  end
70
74
 
75
+ def start_cloud(cloud)
76
+ put("/apps/#{cloud}/start")
77
+ end
78
+
79
+ def stop_cloud(cloud)
80
+ put("/apps/#{cloud}/stop")
81
+ end
82
+
71
83
  def apps
72
84
  get("/apps")
73
85
  end
74
86
 
87
+ def cloud_logs(cloud)
88
+ get("/apps/#{cloud}/deploys")
89
+ end
90
+
75
91
  def ssh_key_available?(ssh_key)
76
92
  get("/users/new", :ssh_key => ssh_key)
77
93
  end
78
94
 
79
- def apps_users(apps)
80
- apps.map do |app|
81
- get("/apps/#{app}/users")
82
- end
95
+ def app_users(cloud)
96
+ get("/apps/#{cloud}/users")
83
97
  end
84
98
 
85
- def apps_ips(apps)
86
- apps.map do |app|
87
- get("/apps/#{app}/ips")
88
- end
99
+ def app_ips(cloud)
100
+ get("/apps/#{cloud}/ips")
89
101
  end
90
102
 
91
103
  def post(path, params = {})
@@ -100,6 +112,10 @@ module Shelly
100
112
  request(path, :get, params)
101
113
  end
102
114
 
115
+ def delete(path, params = {})
116
+ request(path, :delete, params)
117
+ end
118
+
103
119
  def request(path, method, params = {})
104
120
  options = request_parameters(path, method, params)
105
121
  RestClient::Request.execute(options) do |response, request|
@@ -36,23 +36,5 @@ module Shelly
36
36
  # FIXME: check if it possible to remove sub("---", "") by passing options to_yaml
37
37
  string.sub("---","").split("\n").map(&:rstrip).join("\n").strip
38
38
  end
39
-
40
- def fetch_users
41
- response = shelly.apps_users(clouds)
42
- response.inject({}) do |result, app|
43
- result[app['code_name']] = app['users'].map do |user|
44
- user['email']
45
- end
46
- result
47
- end
48
- end
49
-
50
- def fetch_ips
51
- response = shelly.apps_ips(clouds)
52
- response.each do |result|
53
- result
54
- end
55
- end
56
39
  end
57
40
  end
58
-
@@ -27,19 +27,51 @@ module Shelly
27
27
  say_error "Email can't be blank, please try again"
28
28
  end
29
29
 
30
- def check_clouds
31
- @cloudfile = Shelly::Cloudfile.new
32
- @user = Shelly::User.new
33
- user_apps = @user.apps.map { |cloud| cloud['code_name'] }
34
- unless @cloudfile.clouds.all? { |cloud| user_apps.include?(cloud) }
35
- errors = (@cloudfile.clouds - user_apps).map do |cloud|
36
- "You have no access to '#{cloud}' cloud defined in Cloudfile"
37
- end
38
- raise Shelly::Client::APIError.new({:message => "Unauthorized",
39
- :errors => errors}.to_json)
30
+ def ask_to_delete_files
31
+ delete_files_question = "I want to delete all files stored on Shelly Cloud (yes/no):"
32
+ delete_files = ask(delete_files_question)
33
+ exit 1 unless delete_files == "yes"
34
+ end
35
+
36
+ def ask_to_delete_database
37
+ delete_database_question = "I want to delete all database data stored on Shelly Cloud (yes/no):"
38
+ delete_database = ask(delete_database_question)
39
+ exit 1 unless delete_database == "yes"
40
+ end
41
+
42
+ def ask_to_delete_application
43
+ delete_application_question = "I want to delete the application (yes/no):"
44
+ delete_application = ask(delete_application_question)
45
+ exit 1 unless delete_application == "yes"
46
+ end
47
+
48
+ def logged_in?
49
+ user = Shelly::User.new
50
+ user.token
51
+ user
52
+ rescue Client::APIError => e
53
+ if e.unauthorized?
54
+ say_error "You are not logged in. To log in use:", :with_exit => false
55
+ say " shelly login"
56
+ exit 1
57
+ end
58
+ end
59
+
60
+ def multiple_clouds(cloud, action, message)
61
+ clouds = Cloudfile.new.clouds
62
+ if clouds.count > 1 && cloud.nil?
63
+ say "You have multiple clouds in Cloudfile. #{message}"
64
+ say " shelly #{action} #{clouds.first}"
65
+ say "Available clouds:"
66
+ clouds.each do |cloud|
67
+ say " * #{cloud}"
40
68
  end
41
- [@cloudfile, @user]
69
+ exit 1
42
70
  end
71
+ @app = Shelly::App.new
72
+ @app.code_name = cloud.nil? ? clouds.first : cloud
73
+ end
74
+
43
75
  end
44
76
  end
45
77