ubalo 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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: []