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