ubalo 0.1 → 0.2

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/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