ubalo 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
data/bin/ubalo CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'gli'
4
+ include GLI
5
+
4
6
  require 'yaml'
5
7
  require 'fileutils'
6
8
 
@@ -8,63 +10,38 @@ require 'highline'
8
10
  hl = HighLine.new
9
11
 
10
12
  require 'ubalo'
11
- require 'ubalo/version'
12
- include GLI
13
-
14
- use_openstruct true
15
-
16
- class Ubalo
17
- class << self
18
- def config
19
- if @config
20
- @config
21
- elsif File.exists?(config_path)
22
- @config = YAML.load_file(config_path)
23
- else
24
- {}
25
- end
26
- end
27
-
28
- def write_config new_config
29
- h = config.merge(new_config)
30
- File.open(config_path, 'w') do |f|
31
- YAML.dump(h, f)
32
- end
33
- config
34
- end
13
+ include Ubalo
35
14
 
36
- private
37
- def config_path
38
- File.expand_path("~/.ubalorc")
39
- end
15
+ module Ubalo
16
+ def config_filename
17
+ File.expand_path("~/.ubaloconfig")
40
18
  end
41
19
  end
42
20
 
43
- def ubalo
44
- @ubalo
45
- end
21
+ use_openstruct true
46
22
 
47
- def local_username
48
- @local_username || raise(UbaloMessage, "Please re-login to obtain local username")
23
+ logger = Util::Logger.new
24
+
25
+ def connect_url
26
+ @connect_url
49
27
  end
50
28
 
51
- def normalize_pod_name(pod_name)
52
- if pod_name
53
- if pod_name[/\//]
54
- pod_name
55
- else
56
- "#{local_username}/#{pod_name}"
57
- end
58
- else
59
- raise UbaloMessage, "Please specify a pod name"
29
+ def account
30
+ unless @account.authorized?
31
+ raise Ubalo::Error, "Could not find your credentials. Please run ubalo login."
60
32
  end
33
+ @account
61
34
  end
62
35
 
63
- def local_config
64
- unless @local_config
65
- raise UbaloMessage, "Please run this command from within a local pod directory"
36
+ def pod_dir
37
+ @pod_dir
38
+ end
39
+
40
+ def pod
41
+ unless @pod
42
+ @pod = account.pod_or_create_by_name!(pod_dir.name)
66
43
  end
67
- @local_config
44
+ @pod
68
45
  end
69
46
 
70
47
  program_desc 'Command-line access to ubalo.com.'
@@ -72,79 +49,74 @@ program_desc 'Command-line access to ubalo.com.'
72
49
  desc "Change the connect url"
73
50
  flag 'connect-url'
74
51
 
75
- desc "Prints the current version of the Ubalo gem"
76
- switch 'version'
52
+ desc "Override the active pod"
53
+ flag 'pod'
54
+
55
+ desc "Override the active pod directory"
56
+ flag 'pod-dir'
57
+
58
+ desc 'Display version'
59
+ command :version do |c|
60
+ c.action do |global_options,options,args|
61
+ puts Ubalo::VERSION
62
+ end
63
+ end
77
64
 
78
65
  desc 'Display authentication information'
79
66
  command :whoami do |c|
80
67
  c.action do |global_options,options,args|
81
- puts ubalo.whoami
68
+ puts "You are logged in as #{account.username}."
69
+ end
70
+ end
71
+
72
+ desc 'Enter username and password for access'
73
+ command :login do |c|
74
+ c.desc "Username"
75
+ c.flag :username
76
+ c.desc "Password"
77
+ c.flag :password
78
+
79
+ c.action do |global_options,options,args|
80
+ unless options[:username] and options[:password]
81
+ puts "Please enter your Ubalo details for command-line access."
82
+ end
83
+
84
+ login = options[:username] || hl.ask(" login: "){|q| q.echo = true}
85
+ password = options[:password] || hl.ask(" password: "){|q| q.echo = false}
86
+
87
+ @account.authorize(login, password)
88
+ Util.append_config(Ubalo.config_filename, account.base_url, account.as_hash)
89
+ logger.puts "Success! Ready to use as #{account.username}."
82
90
  end
83
91
  end
84
92
 
85
93
  desc 'Display a list of available pods'
86
94
  command :pods do |c|
87
95
  c.action do |global_options,options,args|
88
- pods = ubalo.pods
89
- if pods.empty?
90
- $stderr.puts "You do not currently have any pods."
91
- else
92
- pods.each do |pod|
93
- puts Ubalo.format_pod(pod)
94
- end
96
+ account.pods.each do |pod|
97
+ puts "#{pod.fullname} #{pod.state} updated #{Util.time_ago_in_words pod.updated_at}"
95
98
  end
96
99
  end
97
100
  end
98
101
 
99
102
  desc 'Display tasks'
100
103
  command :tasks do |c|
101
- c.desc 'maximum number of tasks to display'
102
- c.default_value 20
103
- c.flag 'max'
104
-
105
104
  c.action do |global_options,options,args|
106
- ubalo.tasks(options.max).reverse.each do |task|
107
- puts Ubalo.format_task(task)
105
+ account.tasks.each do |task|
106
+ puts "#{task.label} (#{task.state}) #{task.pod_name} #{Util.time_ago_in_words task.submitted_at}"
108
107
  end
109
108
  end
110
109
  end
111
110
 
112
- def process_download pod_response, destination_path=nil
113
- name = pod_response.fetch('name')
114
- fullname = pod_response.fetch('fullname')
115
- pod_url = pod_response.fetch('url')
116
- files_url = pod_response.fetch('archive').fetch('get_url')
117
-
118
- destination_path ||= name
119
-
120
- if File.exists?(destination_path)
121
- raise UbaloMessage, "directory #{destination_path.inspect} already exists"
122
- end
123
-
124
- FileUtils.mkdir(destination_path)
125
- FileUtils.mkdir(File.join(destination_path, ".ubalo"))
126
-
127
- ubalo.retrieve_files(destination_path, files_url)
128
-
129
- File.open(File.join(destination_path, ".ubalo", "config"), "w") do |f|
130
- f.puts(YAML.dump("pod_url" => pod_url))
131
- end
132
-
133
- [fullname, destination_path]
134
- end
135
-
136
111
  desc 'Clone a pod to your computer'
137
112
  arg_name '<pod name>'
138
113
  command :clone do |c|
139
114
  c.action do |global_options,options,args|
140
- pod_name = normalize_pod_name(args.shift)
141
- destination_path = args.shift
142
-
143
- $stderr.print "Fetching #{pod_name}..."
144
- pod_response = ubalo.clone(pod_name)
145
-
146
- fullname, destination_path = process_download(pod_response)
147
- $stderr.puts " saved to #{destination_path}."
115
+ pod = account.pod_by_name!(args.shift)
116
+ pod_dir = PodDir.new(args.shift || pod.name)
117
+ logger.print "Fetching #{pod.fullname} to #{pod_dir.path}..."
118
+ pod.clone_to(pod_dir)
119
+ logger.puts " saved."
148
120
  end
149
121
  end
150
122
 
@@ -154,179 +126,123 @@ command :delete do |c|
154
126
  c.switch :force
155
127
 
156
128
  c.action do |global_options,options,args|
157
- pod_name = normalize_pod_name(args.shift)
129
+ pod = account.pod_by_name!(args.shift)
158
130
 
159
131
  if options.force
160
- ubalo.delete_pod(pod_name)
132
+ pod.delete!
161
133
  else
162
134
  hl.choose do |menu|
163
- menu.prompt = "Are you sure you want to delete #{pod_name}? "
135
+ menu.prompt = "Are you sure you want to delete #{pod.fullname}? "
164
136
  menu.choice :yes do
165
- $stderr.print "Deleting #{pod_name}..."
166
- response = ubalo.delete_pod(pod_name)
167
- $stderr.puts " done."
137
+ pod.delete!
168
138
  end
169
139
  menu.choice :no do
170
- raise UbaloMessage, "Cancelled! No changes made to #{pod_name}."
140
+ raise Ubalo::Error, "Cancelled! No changes made to #{pod.fullname}."
171
141
  end
172
142
  end
173
143
  end
144
+ if pod.deleted?
145
+ logger.puts "Deleted #{pod.fullname}."
146
+ end
174
147
  end
175
148
  end
176
149
 
177
- desc 'Run a pod'
178
- arg_name '<pod name>'
179
- command :run do |c|
150
+ desc 'Initialize this directory as a pod directory'
151
+ command :init do |c|
152
+ c.desc "Pod name"
153
+ c.flag :name
154
+
180
155
  c.action do |global_options,options,args|
181
- pod_name = normalize_pod_name(args.shift)
182
- arg = args.join(" ")
183
- $stderr.print "Running #{pod_name}..."
184
-
185
- response = ubalo.submit_task(pod_name, arg)
186
- task_label = response.fetch('label')
187
- result = ubalo.wait_task(task_label)
188
- ubalo.show_result(result)
156
+ name = options[:name] || hl.ask(" pod name: "){|q| q.echo = true}
157
+ pod = account.pod_or_create_by_name!(name)
158
+ pod_dir.write_name(pod.fullname)
159
+ logger.puts "Success! Next, run ubalo push."
189
160
  end
190
161
  end
191
162
 
192
- desc 'Submit a pod for running in the background'
193
- arg_name '<pod name>'
194
- command :submit do |c|
163
+ desc 'Run a pod'
164
+ command :run do |c|
195
165
  c.action do |global_options,options,args|
196
- pod_name = normalize_pod_name(args.shift)
197
-
198
- $stderr.print "Submitting #{pod_name}..."
199
- result = ubalo.submit_task(pod_name, args.join(" "))
200
- $stderr.puts " submitted."
201
-
202
- ubalo.show_result(result)
166
+ logger.puts "Running #{pod.fullname}."
167
+ task = pod.run(nil)
168
+ logger.poll_on("Waiting for task #{task.label.inspect}") do
169
+ task.refresh!
170
+ task.complete?
171
+ end
172
+ puts task.printable_result
203
173
  end
204
174
  end
205
175
 
206
- desc 'Check or get the result of a task'
207
- arg_name '<task label>'
208
- command :check do |c|
176
+ desc 'Run a pod in the background'
177
+ command :submit do |c|
209
178
  c.action do |global_options,options,args|
210
- task_label = args.first
211
-
212
- $stderr.print "Checking task #{task_label}..."
213
- result = ubalo.check_task(task_label)
214
- $stderr.puts " found."
215
-
216
- ubalo.show_result(result)
179
+ logger.print "Submitting #{pod.fullname}..."
180
+ task = pod.run(nil)
181
+ logger.puts " done."
182
+ puts task.printable_result
217
183
  end
218
184
  end
219
185
 
220
- desc 'Pushes a pod directory directly to Ubalo'
221
- command :push_to do |c|
186
+ desc 'Get the status of a task'
187
+ arg_name '<task label>'
188
+ command :task do |c|
222
189
  c.action do |global_options,options,args|
223
- pod_name = normalize_pod_name(args.shift)
224
-
225
- # First ensure that the pod exists.
226
- pod_url = "#{ubalo.base_url}/pods/#{pod_name}"
227
- pod = ubalo.create_or_update(pod_url)
228
-
229
- # Now push the files archive.
230
- ubalo.push_files(pod_url)
190
+ unless label = args.shift
191
+ raise Ubalo::Error, "please specify a task label"
192
+ end
193
+ task = account.task_by_label!(label)
194
+ logger.print "Getting the status for task #{task.label.inspect}..."
195
+ task.refresh!
196
+ logger.puts " found."
197
+ puts task.printable_result
231
198
  end
232
199
  end
233
200
 
234
201
  desc 'Push files to Ubalo'
235
202
  command :push do |c|
236
203
  c.action do |global_options,options,args|
237
- pod_url = local_config.fetch('pod_url')
238
- pod = ubalo.create_or_update(pod_url)
239
-
240
- ubalo.push_files(pod_url)
241
- end
242
- end
243
-
244
- desc 'Enter username and password for access'
245
- command :login do |c|
246
- c.desc "Username"
247
- c.flag :username
248
- c.desc "Password"
249
- c.flag :password
250
-
251
- c.action do |global_options,options,args|
252
- unless options[:username] and options[:password]
253
- puts "Please enter your Ubalo details for command-line access."
204
+ logger.print "Pushing files to #{pod.fullname.inspect}..."
205
+ pod.push_from(pod_dir)
206
+ logger.puts " done."
207
+ logger.poll_on("Waiting for #{pod.fullname.inspect} to compile") do
208
+ pod.refresh!
209
+ pod.compiled?
254
210
  end
255
-
256
- login = options[:username] || hl.ask(" login: "){|q| q.echo = true}
257
- password = options[:password] || hl.ask(" password: "){|q| q.echo = false}
258
-
259
- response = ubalo.get_authorization(login, password)
260
- ubalo.authorization = response.fetch('authorization')
261
- username = response.fetch('username')
262
-
263
- Ubalo.write_config(ubalo.base_url => {'authorization' => ubalo.authorization, 'username' => username})
264
- puts "Success! Ready to use as #{username}."
265
211
  end
266
212
  end
267
213
 
268
- def unauthorized_command?(command)
269
- command && [:login, :help].include?(command.name)
270
- end
271
-
272
214
  pre do |global,command,options,args|
273
- config = Ubalo.config
274
- connect_url = global['connect-url'] || config['connect-url'] || "https://ubalo.com"
215
+ @connect_url = ENV['UBALO_CONNECT_URL'] || "https://ubalo.com"
275
216
 
276
- if global[:version]
277
- print "Ubalo version #{Ubalo::VERSION}"
278
- raise UbaloOKExit
279
- end
280
-
281
- # When logging in, don't use the authorization:
282
- unless unauthorized_command?(command)
283
- if host_config = config[connect_url]
284
- @local_username = host_config['username']
285
- authorization = host_config['authorization']
286
- end
287
- end
288
-
289
- unless authorization or unauthorized_command?(command)
290
- raise UbaloMessage, "No credentials found. Please run 'ubalo login'."
217
+ config = Util.read_config(Ubalo.config_filename)
218
+ if account_config = config[connect_url]
219
+ @account = Account.from_hash(account_config)
220
+ else
221
+ @account = Account.new(connect_url)
291
222
  end
292
223
 
293
- local_config_filename = ".ubalo/config"
294
- if File.exists?(local_config_filename)
295
- full_path = File.expand_path(local_config_filename)
296
- @local_config = YAML.load_file(full_path)
224
+ if pod_name = global['pod']
225
+ @pod = account.pod_or_create_by_name!(pod_name)
297
226
  end
298
227
 
299
- @ubalo ||= Ubalo.login(authorization, connect_url)
228
+ @pod_dir = PodDir.new(global['pod-dir'] || ".")
300
229
 
301
230
  true
302
231
  end
303
232
 
304
233
  on_error do |exception|
305
- puts
234
+ logger.complete_line
306
235
  case exception
307
- when RestClient::BadRequest
308
- $stderr.puts exception.inspect.sub('400 Bad Request', 'Error')
309
- false # no additional raise required.
310
- when RestClient::ResourceNotFound
311
- $stderr.puts exception.inspect
312
- false # no additional raise required.
313
- when RestClient::Unauthorized, RestClient::Forbidden
314
- $stderr.puts "Invalid credentials. Please run ubalo login."
315
- true # fall back to GLI's handling
316
- when GLI::BadCommandLine
317
- true # fall back to GLI's handling
318
- when UbaloOKExit
319
- # Normal exit, preserving the correct exit code.
320
- exit 0
321
- when UbaloMessage
322
- $stderr.puts "Error: #{exception.message}"
236
+ when Ubalo::Error, GLI::UnknownCommand
237
+ logger.complete_line
238
+ logger.puts "Error: #{exception.message}"
323
239
  exit 1
324
240
  when Interrupt
325
- $stderr.puts "Cancelled!"
241
+ logger.complete_line
242
+ logger.puts "Cancelled!"
326
243
  exit 1
327
244
  else
328
- $stderr.puts "Error! Unfortunately something went wrong. Please contact us: errors@ubalo.com."
329
- raise # and go on to raise
245
+ raise
330
246
  end
331
247
  end
332
248
 
@@ -0,0 +1,93 @@
1
+ module Ubalo
2
+ class Account
3
+ def self.from_hash(h)
4
+ account = new h.fetch(:base_url)
5
+ account.username = h.fetch(:username)
6
+ account.authorization = h.fetch(:authorization)
7
+ account
8
+ end
9
+
10
+ attr_accessor :username, :authorization
11
+ attr_reader :base_url
12
+
13
+ def initialize(base_url)
14
+ @base_url = base_url
15
+ end
16
+
17
+ def authorized?
18
+ !!@authorization
19
+ end
20
+
21
+ def request method, path = nil, options = {}
22
+ if authorization
23
+ options[:authorization] = authorization
24
+ end
25
+ Util.http_request(method, "#{@base_url}#{path}", options)
26
+ end
27
+
28
+ def authorize(login, password)
29
+ response = request(:post, "/user/authorization", :params => {:user => {:login => login, :password => password}})
30
+ @username = response.fetch("username")
31
+ @authorization = response.fetch("authorization")
32
+ rescue RestClient::BadRequest
33
+ raise Ubalo::Error, "Incorrect login or password"
34
+ end
35
+
36
+ def pod_from_json(pod_json)
37
+ Pod.create_with_json(self, pod_json)
38
+ end
39
+
40
+ def task_from_json(task_json)
41
+ Task.create_with_json(self, task_json)
42
+ end
43
+
44
+ def archive_from_json(archive_json)
45
+ PodArchive.create_with_json(self, archive_json)
46
+ end
47
+
48
+ def pods
49
+ request(:get, "/pods").map do |pod_json|
50
+ pod_from_json(pod_json)
51
+ end
52
+ end
53
+
54
+ def pod_by_name!(name)
55
+ pod_username, pod_name = Util.normalize_pod_name(username, name)
56
+ pod = Pod.new(self, pod_username, pod_name, {})
57
+ pod.refresh!
58
+ end
59
+
60
+ def pod_or_create_by_name!(name)
61
+ pod_username, pod_name = Util.normalize_pod_name(username, name)
62
+ pod = Pod.new(self, pod_username, pod_name, {})
63
+ pod.update!
64
+ end
65
+
66
+ def task_by_label!(label)
67
+ task = Task.new(self, label, {})
68
+ task.refresh!
69
+ end
70
+
71
+ def tasks
72
+ request(:get, "/tasks").map do |task_json|
73
+ task_from_json(task_json)
74
+ end
75
+ end
76
+
77
+ def as_hash
78
+ {
79
+ :base_url => @base_url,
80
+ :username => username,
81
+ :authorization => authorization
82
+ }
83
+ end
84
+
85
+ def inspect
86
+ "#<Account #{username} #{base_url} #{'authorized' if authorization}>"
87
+ end
88
+
89
+ def ==(other)
90
+ [base_url, username, authorization] == [base_url, other.username, other.authorization]
91
+ end
92
+ end
93
+ end
data/lib/ubalo/pod.rb ADDED
@@ -0,0 +1,99 @@
1
+ module Ubalo
2
+ class Pod
3
+ include Ubalo::Util
4
+
5
+ def self.create_with_json(account, json)
6
+ new(account, json.fetch('user').fetch('username'), json.fetch('name'), json)
7
+ end
8
+
9
+ attr_reader :username, :name, :state, :archive, :updated_at
10
+
11
+ def initialize(account, username, name, attributes)
12
+ @account = account
13
+ @username = username
14
+ @name = name
15
+ update_attributes(attributes)
16
+ end
17
+
18
+ def update!
19
+ update_attributes(request(:put))
20
+ rescue RestClient::ResourceNotFound
21
+ raise Ubalo::Error, "Could not update pod #{fullname.inspect}"
22
+ end
23
+
24
+ def refresh!
25
+ update_attributes(request(:get))
26
+ rescue RestClient::ResourceNotFound
27
+ raise Ubalo::Error, "Could not find pod #{fullname.inspect}"
28
+ end
29
+
30
+ def fullname
31
+ "#{@username}/#{@name}"
32
+ end
33
+
34
+ def tasks
35
+ request(:get, "/tasks").map do |task_json|
36
+ @account.task_from_json(task_json)
37
+ end
38
+ end
39
+
40
+ def run(arg)
41
+ params = {
42
+ :arg_content_type => UbaloJSON.content_type,
43
+ :arg => UbaloJSON.dump(arg)
44
+ }
45
+ @account.task_from_json(request(:post, "/tasks", :params => params))
46
+ end
47
+
48
+ def push_from(pod_dir)
49
+ new_archive.activate_from(pod_dir)
50
+ refresh!
51
+ end
52
+
53
+ def clone_to(pod_dir)
54
+ refresh!
55
+ archive.extract_to(pod_dir)
56
+ pod_dir.write_name(fullname)
57
+ end
58
+
59
+ def compiled?
60
+ state == 'compiled'
61
+ end
62
+
63
+ def delete!
64
+ request(:delete, nil, :parse => false)
65
+ @deleted = true
66
+ end
67
+
68
+ def deleted?
69
+ !!@deleted
70
+ end
71
+
72
+ def inspect
73
+ "#<Pod #{fullname.inspect} state=#{state.inspect}>"
74
+ end
75
+
76
+ def ==(other)
77
+ fullname == other.fullname
78
+ end
79
+
80
+ private
81
+ def update_attributes(attributes)
82
+ @state = attributes['state']
83
+ if archive_attr = attributes['archive']
84
+ @archive = @account.archive_from_json(archive_attr)
85
+ end
86
+ @updated_at = attributes['updated_at']
87
+ @deleted = false
88
+ self
89
+ end
90
+
91
+ def new_archive
92
+ @account.archive_from_json(request(:post, "/archives"))
93
+ end
94
+
95
+ def request method, path = nil, options = {}
96
+ @account.request(method, "/pods/#{fullname}#{path}", options)
97
+ end
98
+ end
99
+ end