ubalo 0.0.1 → 0.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.
Files changed (3) hide show
  1. data/bin/ubalo +332 -0
  2. data/lib/ubalo.rb +210 -0
  3. metadata +47 -11
data/bin/ubalo ADDED
@@ -0,0 +1,332 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'gli'
4
+ require 'yaml'
5
+
6
+ require 'highline'
7
+ require 'highline/import'
8
+ hl = HighLine.new
9
+
10
+ require 'ubalo'
11
+ include GLI
12
+
13
+ use_openstruct true
14
+
15
+ class Ubalo
16
+ class << self
17
+ def config
18
+ if @config
19
+ @config
20
+ elsif File.exists?(config_path)
21
+ @config = YAML.load_file(config_path)
22
+ else
23
+ {}
24
+ end
25
+ end
26
+
27
+ def write_config new_config
28
+ h = config.merge(new_config)
29
+ File.open(config_path, 'w') do |f|
30
+ YAML.dump(h, f)
31
+ end
32
+ config
33
+ end
34
+
35
+ private
36
+ def config_path
37
+ File.expand_path("~/.ubalorc")
38
+ end
39
+ end
40
+ end
41
+
42
+ def ubalo
43
+ @ubalo
44
+ end
45
+
46
+ program_desc 'Command-line access to ubalo.com.'
47
+ version "0.0.1"
48
+
49
+ desc "Change the connect url"
50
+ flag 'connect-url'
51
+
52
+ desc 'Display authentication information'
53
+ command :whoami do |c|
54
+ c.action do |global_options,options,args|
55
+ puts ubalo.whoami
56
+ end
57
+ end
58
+
59
+ desc 'Display pods'
60
+ command :pods do |c|
61
+ c.desc 'show all pods you can snap (default)'
62
+ c.switch :all
63
+
64
+ c.desc 'show only your pods'
65
+ c.switch :mine
66
+
67
+ c.desc 'show only the pods you can edit and snap'
68
+ c.switch :editable
69
+
70
+ c.action do |global_options,options,args|
71
+ unless options.all or options.edit_only
72
+ # Default to showing just your pods.
73
+ options.mine = true
74
+ end
75
+
76
+ your_pods, editable_pods, snappable_pods = ubalo.pods
77
+
78
+ # Always show your pods.
79
+ your_pods.each do |pod|
80
+ puts Ubalo.format_pod(pod)
81
+ end
82
+
83
+ if options.all or options.edit_only
84
+ editable_pods.each do |pod|
85
+ puts Ubalo.format_pod(pod)
86
+ end
87
+ end
88
+
89
+ if options.all
90
+ snappable_pods.each do |pod|
91
+ puts Ubalo.format_pod(pod)
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ desc 'Display tasks'
98
+ command :tasks do |c|
99
+ c.desc 'filter by task state (pending, complete, etc)'
100
+ c.long_desc 'possibilities are waiting,running,pending,complete,failed,all'
101
+ c.default_value 'all'
102
+ c.flag 'state'
103
+
104
+ c.desc 'maximum number of tasks to display'
105
+ c.default_value 20
106
+ c.flag 'max'
107
+
108
+ c.action do |global_options,options,args|
109
+ unless options.pending or options.failed
110
+ # Default to showing just your pods.
111
+ options.all = true
112
+ end
113
+
114
+ ubalo.tasks(options.state, options.max).each do |task|
115
+ puts Ubalo.format_task(task)
116
+ end
117
+ end
118
+ end
119
+
120
+ desc 'Add an ssh key (defaults to ~/.ssh/id_rsa.pub)'
121
+ arg_name '<file containing ssh key>'
122
+ command :add_key do |c|
123
+ c.action do |global_options,options,args|
124
+
125
+ public_key_file, = *args
126
+
127
+ public_key_file ||= '~/.ssh/id_rsa.pub'
128
+ public_key_file = File.expand_path(public_key_file)
129
+
130
+ puts ubalo.upload_key(File.read(public_key_file))
131
+ end
132
+ end
133
+
134
+ desc 'Clear all ssh keys'
135
+ command :clear_keys do |c|
136
+ c.action do |global_options,options,args|
137
+ puts ubalo.clear_keys
138
+ end
139
+ end
140
+
141
+ desc 'Modify a pod'
142
+ arg_name '<pod name>'
143
+ command :edit do |c|
144
+ c.action do |global_options,options,args|
145
+ pod_name = args.shift
146
+ unless pod_name
147
+ raise "please specify a pod"
148
+ end
149
+
150
+ ubalo.ssh(pod_name)
151
+ end
152
+ end
153
+
154
+ desc 'Show the code in a pod'
155
+ arg_name '<pod name>'
156
+ command :cat do |c|
157
+ c.action do |global_options,options,args|
158
+ pod_name = args.shift
159
+ unless pod_name
160
+ raise "please specify a pod"
161
+ end
162
+
163
+ puts ubalo.cat_code(pod_name)
164
+ end
165
+ end
166
+
167
+ desc 'Set the code in a pod'
168
+ arg_name '<pod_name> <code_file>'
169
+ command :code do |c|
170
+ c.action do |global_options,options,args|
171
+ pod_name = args.shift
172
+ unless pod_name
173
+ raise "please specify a pod"
174
+ end
175
+
176
+ file_name = args.shift
177
+ if file_name
178
+ code = File.read(file_name)
179
+ elsif $stdin.tty?
180
+ raise "please give the new code as a filename or on stdin"
181
+ else
182
+ code = $stdin.read
183
+ end
184
+
185
+ puts ubalo.upload_code(pod_name, code)
186
+ end
187
+ end
188
+
189
+ desc 'Show the files in a pod environ'
190
+ arg_name '<pod name>'
191
+ command :ls do |c|
192
+ c.desc 'list in long format'
193
+ c.switch :l
194
+
195
+ c.desc 'list in human-readable form'
196
+ c.switch :h
197
+
198
+ c.action do |global_options,options,args|
199
+ opts = ""
200
+ opts << " -l" if options.l
201
+ opts << " -h" if options.h
202
+
203
+ pod_name = args.shift
204
+ unless pod_name
205
+ raise "please specify a pod"
206
+ end
207
+ puts ubalo.ls(pod_name, opts)
208
+ end
209
+ end
210
+
211
+ desc 'Copy files into and out of a pod environ'
212
+ arg_name '<pod name>'
213
+ command :cp do |c|
214
+ c.desc 'copy recursively'
215
+ c.switch :r
216
+
217
+ c.action do |global_options,options,args|
218
+ opts = ""
219
+ opts << " -r" if options.r
220
+
221
+ scp_args = args.join(" ").gsub(/[\w-]+(?=:)/) do |pod_name|
222
+ ssh_path, environ = ubalo.ssh_path(pod_name)
223
+ "#{ssh_path}:#{environ}"
224
+ end
225
+
226
+ ubalo.scp(opts, scp_args)
227
+ end
228
+ end
229
+
230
+ desc 'Snap a pod'
231
+ arg_name '<pod name>'
232
+ command :snap do |c|
233
+ c.desc 'detach, leaving task running in the background; return an id'
234
+ c.switch [:detach, :d]
235
+
236
+ c.desc 'explicitly show the complete result'
237
+ c.switch [:verbose, :v]
238
+
239
+ c.desc 'outputs as json to stdout; stdout > stderr'
240
+ c.switch [:json, :j]
241
+
242
+ c.action do |global_options,options,args|
243
+ pod_name = args.shift
244
+ unless pod_name
245
+ raise "please specify a pod"
246
+ end
247
+
248
+ h = ubalo.submit_task(pod_name, args.join(" "))
249
+ task_id = h['id']
250
+
251
+ if options.detach
252
+ puts task_id
253
+ else
254
+ result = ubalo.check_task(task_id, true)
255
+ ubalo.show_result(result, options.verbose, options.json)
256
+ end
257
+ end
258
+ end
259
+
260
+ desc 'Check or get the result of a task'
261
+ arg_name '<task id>'
262
+ command :check do |c|
263
+ c.desc 'explicitly show the complete result'
264
+ c.switch [:verbose, :v]
265
+
266
+ c.desc 'wait for the result if the task is pending'
267
+ c.switch [:wait, :w]
268
+
269
+ c.desc 'outputs as json to stdout; stdout > stderr'
270
+ c.switch [:json, :j]
271
+
272
+ c.action do |global_options,options,args|
273
+ task_id = args.shift
274
+ unless task_id
275
+ raise "please specify a task"
276
+ end
277
+
278
+ result = ubalo.check_task(task_id, true)
279
+ ubalo.show_result(result, options.verbose, options.json)
280
+ end
281
+ end
282
+
283
+ desc 'Retrieve an API token'
284
+ command :authorize do |c|
285
+ c.action do |global_options,options,args|
286
+ puts "Please enter your Ubalo username/email and password to unlock command-line access."
287
+ login = hl.ask(" login: "){|q| q.echo = true}
288
+ password = hl.ask(" password: "){|q| q.echo = false}
289
+
290
+ ubalo.token = ubalo.get_api_token(login, password)
291
+ Ubalo.write_config('token' => ubalo.token)
292
+ puts "Access details saved to ~/.ubalorc."
293
+ puts ubalo.whoami
294
+ end
295
+ end
296
+
297
+ desc 'Clear any stored API tokens'
298
+ command :deauthorize do |c|
299
+ c.action do |global_options,options,args|
300
+ Ubalo.write_config('token' => nil)
301
+ puts "Access details deleted."
302
+ end
303
+ end
304
+
305
+ pre do |global,command,options,args|
306
+ config = Ubalo.config
307
+ token = config['token']
308
+
309
+ unless token or (command && command.name == :authorize)
310
+ raise "No credentials found. Please run 'ubalo authorize'."
311
+ end
312
+
313
+ connect_url = global['connect-url'] || config['connect-url']
314
+ @ubalo ||= Ubalo.authorize(token, connect_url)
315
+
316
+ true
317
+ end
318
+
319
+ on_error do |exception|
320
+ case exception
321
+ when RestClient::BadRequest
322
+ $stderr.puts exception.inspect.sub('400 Bad Request', 'Error')
323
+ false
324
+ when UbaloExit
325
+ # Normal exit, preserving the correct exit code.
326
+ exit exception.message
327
+ else
328
+ true
329
+ end
330
+ end
331
+
332
+ exit GLI.run(ARGV)
data/lib/ubalo.rb ADDED
@@ -0,0 +1,210 @@
1
+ require 'rest-client'
2
+ require 'json'
3
+
4
+ class String
5
+ def lfit max_length
6
+ if length - 3 <= max_length
7
+ ljust(max_length)
8
+ else
9
+ "#{self[0,max_length-3]}..."
10
+ end
11
+ end
12
+
13
+ def rfit max_length
14
+ if length - 3 <= max_length
15
+ rjust(max_length)
16
+ else
17
+ "#{self[0,max_length-3]}..."
18
+ end
19
+ end
20
+
21
+ def indent amount=2
22
+ each_line.map do |l|
23
+ " "*amount + l
24
+ end.join
25
+ end
26
+ end
27
+
28
+ class NilClass
29
+ def lfit max_length
30
+ "".lfit(max_length)
31
+ end
32
+
33
+ def rfit max_length
34
+ "".rfit(max_length)
35
+ end
36
+
37
+ def indent amount
38
+ "".indent(amount)
39
+ end
40
+ end
41
+
42
+ class UbaloExit < StandardError
43
+
44
+ end
45
+
46
+ class Ubalo
47
+ class << self
48
+ def format_task task
49
+ "#{task['id'].to_s[0,6]} #{task['state'].lfit(8)} #{task['pod_name'].lfit(25)} #{task['arg'].to_s.lfit(40)}"
50
+ end
51
+
52
+ def format_pod pod
53
+ "#{pod['environ'].lfit(18)} #{pod['code_lines'].to_s.rjust 3} lines #{pod['user'].rfit(10)} / #{pod['name']}"
54
+ end
55
+ end
56
+
57
+ attr_reader :base_url
58
+ attr_accessor :token
59
+
60
+ def self.authorize token, base_url=nil
61
+ new(token, base_url)
62
+ end
63
+
64
+ def initialize token, base_url
65
+ @token = token
66
+ @base_url = base_url || "https://ubalo.com/api"
67
+ end
68
+
69
+ def get(action, params={})
70
+ url = "#{base_url}/#{action}"
71
+ if token
72
+ params.merge!({:auth_token => token})
73
+ end
74
+ response = RestClient.get url, :params => params
75
+ response
76
+ end
77
+
78
+ def post(action, params={})
79
+ url = "#{base_url}/#{action}"
80
+ RestClient.post url, {:auth_token => token}.merge(params)
81
+ end
82
+
83
+ def username
84
+ username, role = whoami
85
+ username
86
+ end
87
+
88
+ def whoami
89
+ get(:whoami)
90
+ end
91
+
92
+ def tasks state, count
93
+ parse(get(:tasks, :count => count, :state => state))
94
+ end
95
+
96
+ def submit_task(pod_name, arg)
97
+ parse(post(:submit_task, {:pod_name => pod_name, :arg => arg}))
98
+ end
99
+
100
+ def check_task(id, blocking=false)
101
+ if blocking
102
+ 10.times do
103
+ h = check_task_once(id)
104
+ return(h) if %w{complete failed}.include?(h['state'])
105
+ sleep 0.5
106
+ end
107
+ raise "timed-out waiting for task"
108
+ else
109
+ check_task_once(id)
110
+ end
111
+ end
112
+
113
+ def upload_key(key)
114
+ post(:upload_key, :key => key)
115
+ end
116
+
117
+ def clear_keys
118
+ post(:clear_keys)
119
+ end
120
+
121
+ def cat_code(name)
122
+ get(:cat_code, :pod_name => name)
123
+ end
124
+
125
+ def upload_code(name, code)
126
+ post(:upload_code, {:pod_name => name, :code => code})
127
+ end
128
+
129
+ def ssh_path(name)
130
+ h = parse(get(:ssh_path, :pod_name => name))
131
+ [h['ssh_path'], h['environ'], h['environ_fullname']]
132
+ end
133
+
134
+ def pods
135
+ h = parse(get(:pods))
136
+ [h['your_pods'], h['editable_pods'], h['snappable_pods']]
137
+ end
138
+
139
+ def ssh(pod_name)
140
+ ssh_path, environ = ssh_path(pod_name)
141
+ puts "Opening an ssh connection to edit the '#{environ}' environment for the '#{pod_name}' pod."
142
+ ssh_command = "ssh -tq #{ssh_path} #{environ}"
143
+ Process.exec({'TERM' => 'xterm'}, ssh_command)
144
+ end
145
+
146
+ def scp(opts, args)
147
+ Process.exec("scp#{opts} #{args}")
148
+ end
149
+
150
+ def ls(pod_name, options)
151
+ ssh_path, environ = ssh_path(pod_name)
152
+ ssh_command = "ssh -tq #{ssh_path} #{environ}"
153
+ `#{ssh_command} ls#{options}`
154
+ end
155
+
156
+ def show_result(result, verbose=false, json=false)
157
+ if result['outputs']
158
+ outputs = JSON.dump(result['outputs'])
159
+ end
160
+
161
+ if json
162
+ puts outputs
163
+ else
164
+ if verbose
165
+ puts " id: #{result['id']}"
166
+ puts " name: #{result['pod_name']}"
167
+ puts " state: #{result['state']}"
168
+ puts "status: #{result['status']}"
169
+ if result['stdout']
170
+ puts "stdout:"
171
+ puts result['stdout'].indent
172
+ end
173
+ if result['stderr']
174
+ puts "stderr:"
175
+ puts result['stderr'].indent
176
+ end
177
+ else
178
+ if result['stdout']
179
+ print result['stdout']
180
+ end
181
+ if result['stderr']
182
+ $stderr.print result['stderr']
183
+ end
184
+
185
+ exit_code = Integer(result['status'] || 0)
186
+ unless exit_code.zero?
187
+ raise UbaloExit, exit_code
188
+ end
189
+ end
190
+
191
+ if outputs
192
+ $stderr.puts "outputs:"
193
+ $stderr.puts outputs.indent
194
+ end
195
+ end
196
+ end
197
+
198
+ def get_api_token login, password
199
+ parse(post(:get_api_token, :user => {:login => login, :password => password}))['api_token']
200
+ end
201
+
202
+ private
203
+ def check_task_once id
204
+ parse(get(:check_task, :id => id))
205
+ end
206
+
207
+ def parse(result)
208
+ JSON.load(result)
209
+ end
210
+ end
metadata CHANGED
@@ -1,23 +1,59 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ubalo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
8
- - TBD
8
+ - Ubalo Crew
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-09-13 00:00:00.000000000Z
13
- dependencies: []
14
- description: i am waiting to upload this
15
- email: thief@example.org
16
- executables: []
12
+ date: 2011-11-22 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: gli
16
+ requirement: &2153389080 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2153389080
25
+ - !ruby/object:Gem::Dependency
26
+ name: highline
27
+ requirement: &2153388480 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *2153388480
36
+ - !ruby/object:Gem::Dependency
37
+ name: rest-client
38
+ requirement: &2153387840 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 1.6.3
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *2153387840
47
+ description: CLI and API client for Ubalo
48
+ email: dev@ubalo.com
49
+ executables:
50
+ - ubalo
17
51
  extensions: []
18
52
  extra_rdoc_files: []
19
- files: []
20
- homepage:
53
+ files:
54
+ - lib/ubalo.rb
55
+ - bin/ubalo
56
+ homepage: http://ubalo.com/
21
57
  licenses: []
22
58
  post_install_message:
23
59
  rdoc_options: []
@@ -37,8 +73,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
37
73
  version: '0'
38
74
  requirements: []
39
75
  rubyforge_project:
40
- rubygems_version: 1.8.7
76
+ rubygems_version: 1.8.6
41
77
  signing_key:
42
78
  specification_version: 3
43
- summary: i am waiting to upload this
79
+ summary: CLI and API client for Ubalo
44
80
  test_files: []