zillabyte-cli 0.0.24 → 0.1.0

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 (40) hide show
  1. checksums.yaml +6 -14
  2. data/lib/#zillabyte-cli.rb# +5 -0
  3. data/lib/zillabyte/api/apps.rb +16 -132
  4. data/lib/zillabyte/api/components.rb +115 -0
  5. data/lib/zillabyte/api/flows.rb +121 -0
  6. data/lib/zillabyte/api/keys.rb +70 -0
  7. data/lib/zillabyte/api.rb +15 -2
  8. data/lib/zillabyte/auth.rb +43 -16
  9. data/lib/zillabyte/cli/#logs.rb# +12 -0
  10. data/lib/zillabyte/cli/#repl.rb# +43 -0
  11. data/lib/zillabyte/cli/apps.rb +52 -893
  12. data/lib/zillabyte/cli/auth.rb +3 -8
  13. data/lib/zillabyte/cli/base.rb +28 -7
  14. data/lib/zillabyte/cli/components.rb +245 -0
  15. data/lib/zillabyte/cli/flows.rb +549 -0
  16. data/lib/zillabyte/cli/git.rb +38 -0
  17. data/lib/zillabyte/cli/help.rb +11 -4
  18. data/lib/zillabyte/cli/keys.rb +177 -0
  19. data/lib/zillabyte/cli/query.rb +0 -1
  20. data/lib/zillabyte/cli/relations.rb +2 -1
  21. data/lib/zillabyte/cli/templates/{js → apps/js}/simple_function.js +0 -0
  22. data/lib/zillabyte/cli/templates/{js → apps/js}/zillabyte.conf.yaml +0 -0
  23. data/lib/zillabyte/cli/templates/apps/python/app.py +17 -0
  24. data/lib/zillabyte/cli/templates/{python → apps/python}/requirements.txt +0 -0
  25. data/lib/zillabyte/cli/templates/{python → apps/python}/zillabyte.conf.yaml +1 -1
  26. data/lib/zillabyte/cli/templates/{ruby → apps/ruby}/Gemfile +0 -0
  27. data/lib/zillabyte/cli/templates/{ruby → apps/ruby}/app.rb +1 -1
  28. data/lib/zillabyte/cli/templates/{ruby → apps/ruby}/zillabyte.conf.yaml +0 -0
  29. data/lib/zillabyte/cli/templates/python/{simple_function.py → #simple_function.py#} +3 -6
  30. data/lib/zillabyte/common/session.rb +3 -1
  31. data/lib/zillabyte/helpers.rb +64 -1
  32. data/lib/zillabyte/runner/app_runner.rb +226 -0
  33. data/lib/zillabyte/runner/component_operation.rb +529 -0
  34. data/lib/zillabyte/runner/component_runner.rb +244 -0
  35. data/lib/zillabyte/runner/multilang_operation.rb +1133 -0
  36. data/lib/zillabyte/runner/operation.rb +11 -0
  37. data/lib/zillabyte/runner.rb +6 -0
  38. data/lib/zillabyte-cli/version.rb +1 -1
  39. data/zillabyte-cli.gemspec +1 -0
  40. metadata +83 -52
@@ -1,5 +1,6 @@
1
- require "zillabyte/cli/base"
1
+ require "zillabyte/cli/flows"
2
2
  require "zillabyte/cli/config"
3
+ require "zillabyte/runner/app_runner"
3
4
  require "zillabyte/common"
4
5
  require "pty"
5
6
  require 'indentation'
@@ -11,7 +12,7 @@ require 'net/http'
11
12
 
12
13
  # manage custom apps
13
14
  #
14
- class Zillabyte::Command::Apps < Zillabyte::Command::Base
15
+ class Zillabyte::Command::Apps < Zillabyte::Command::Flows
15
16
 
16
17
  MAX_POLL_SECONDS = 60 * 15
17
18
  POLL_SLEEP = 0.5
@@ -69,7 +70,7 @@ class Zillabyte::Command::Apps < Zillabyte::Command::Base
69
70
  res = api.request(
70
71
  :expects => 200,
71
72
  :method => :get,
72
- :path => "/flows/#{app_id}/details",
73
+ :path => "/apps/#{app_id}/details",
73
74
  :body => options.to_json
74
75
  )
75
76
  res.body
@@ -193,7 +194,7 @@ class Zillabyte::Command::Apps < Zillabyte::Command::Base
193
194
  def push
194
195
 
195
196
  since = Time.now.utc.to_s
196
- dir = options[:directory] || shift_argument
197
+ dir = options[:directory]
197
198
  if dir.nil?
198
199
  dir = Dir.pwd
199
200
  else
@@ -236,44 +237,8 @@ class Zillabyte::Command::Apps < Zillabyte::Command::Base
236
237
  # $ zillabyte apps:pull .
237
238
  #
238
239
  def pull
239
-
240
- app_id = options[:id] || shift_argument
241
-
242
- if !(app_id =~ /^\d*$/)
243
- options[:is_name] = true
244
- end
245
-
246
- dir = options[:directory] || shift_argument
247
- error("no directory given", type) if dir.nil?
248
- dir = File.expand_path(dir)
249
-
250
- type = options[:output_type]
251
-
252
- error("no id given", type) if app_id.nil?
253
-
254
- # Create if not exists..
255
- if File.exists?(dir)
256
- if Dir.entries(dir).size != 2 and options[:force].nil?
257
- error("target directory not empty. use --force to override", type)
258
- end
259
- else
260
- FileUtils.mkdir_p(dir)
261
- end
262
-
263
- res = api.apps.pull_to_directory app_id, dir, session, options
264
-
265
- if res['error']
266
- error("error: #{res['error_message']}", type)
267
- else
268
- if type == "json"
269
- display "{}"
270
- else
271
- display "app ##{res['id']} pulled to #{dir}"
272
- end
273
- end
274
-
240
+ super
275
241
  end
276
- alias_command "pull", "apps:pull"
277
242
 
278
243
 
279
244
  # apps:delete ID
@@ -284,43 +249,7 @@ class Zillabyte::Command::Apps < Zillabyte::Command::Base
284
249
  # --output_type OUTPUT_TYPE # specify an output type i.e. json
285
250
  #
286
251
  def delete
287
- app_id = options[:id] || shift_argument
288
-
289
- if app_id.nil?
290
- app_id = read_name_from_conf(options)
291
- options[:is_name] = true
292
- elsif !(app_id =~ /^\d*$/)
293
- options[:is_name] = true
294
- end
295
- forced = options[:force]
296
- type = options[:output_type]
297
-
298
- if not forced
299
-
300
- if !type.nil?
301
- error("specify -f, --force to confirm deletion", type)
302
- end
303
-
304
- while true
305
-
306
- display "This operation cannot be undone. Are you sure you want to delete this app? (yes/no):", false
307
- confirm = ask
308
- break if confirm == "yes" || confirm == "no"
309
- display "Please enter 'yes' to delete the app or 'no' to exit"
310
- end
311
- end
312
-
313
- confirmed = forced || confirm == "yes"
314
-
315
- if confirmed
316
- response = api.apps.delete(app_id, options)
317
- if type == "json"
318
- display "{}"
319
- else
320
- display response["body"]
321
- end
322
- end
323
-
252
+ super
324
253
  end
325
254
 
326
255
 
@@ -332,86 +261,60 @@ class Zillabyte::Command::Apps < Zillabyte::Command::Base
332
261
  # --output_type OUTPUT_TYPE # specify an output type i.e. json
333
262
  #
334
263
  def prep
335
-
336
- type = options[:output_type]
337
- dir = options[:directory] || shift_argument
338
- if dir.nil?
339
- dir = Dir.pwd
340
- else
341
- dir = File.expand_path(dir)
342
- end
343
- options[:directory] = dir
344
- meta = Zillabyte::CLI::Config.get_config_info(dir)
345
-
346
- if meta.nil?
347
- error("The specified directory (#{dir}) does not appear to contain a valid Zillabyte configuration file.", type)
348
- end
349
-
350
- case meta["language"]
351
- when "ruby"
352
-
353
- # Execute in the bundler context
354
- full_script = File.join(dir, meta["script"])
355
- cmd = "cd \"#{meta['home_dir']}\"; unset BUNDLE_GEMFILE; unset RUBYOPT; bundle install"
356
- exec(cmd)
357
-
358
- when "python"
359
- vDir = "#{meta['home_dir']}/vEnv"
360
- lock_file = meta['home_dir']+"/zillabyte_thread_lock_file"
361
- if File.exists?(lock_file)
362
- sleep(1) while File.exists?(lock_file)
363
- else
364
- begin
365
- cmd = "touch #{lock_file}; virtualenv --clear --system-site-packages #{vDir}; PYTHONPATH=~/zb1/multilang/python/Zillabyte #{vDir}/bin/pip install -r #{meta['home_dir']}/requirements.txt"
366
- system cmd, :out => :out
367
- ensure
368
- File.delete(lock_file)
369
- end
370
- end
371
-
372
- when "js"
373
- end
374
-
264
+ super
375
265
  end
376
- alias_command "prep", "apps:prep"
377
-
378
-
379
266
 
380
267
 
381
- # apps:init [LANG] [DIR]
268
+ # apps:init [NAME]
382
269
  #
383
- # initializes a new executable in DIR
384
- # [LANG] defaults to ruby, and [DIR] to the current directory
270
+ # initializes a new app
385
271
  #
386
- # --output_type OUTPUT_TYPE # specify an output type i.e. json
387
- # --directory DIR # Directory of the app
272
+ # --lang LANG # which language to use [ruby, python]. default 'ruby'.
273
+ # --dir DIR # target directory of the app.
388
274
  #
389
275
  #Examples:
390
276
  #
391
- # $ zillabyte apps:init python contact_extractor
277
+ # $ zillabyte apps:init contact_extractor --lang python
392
278
  #
393
279
  def init
394
280
 
395
- lang = options[:lang] || shift_argument || "ruby"
396
- dir = options[:directory] || shift_argument
397
- if dir.nil?
398
- dir = Dir.pwd
399
- else
400
- dir = File.expand_path(dir)
401
- end
281
+ name = options[:name] || shift_argument
282
+ lang = options[:lang] || options[:language] || "ruby"
283
+ dir = options[:dir] || options[:directory] || File.join(Dir.pwd, name)
284
+ dir = File.expand_path(dir) unless dir.nil?
402
285
  type = options[:output_type]
403
286
 
404
287
  languages = ["ruby","python", "js"]
405
288
 
406
- error("Unsupported language #{lang}. We only support #{languages.join(', ')}.", type) if not languages.include? lang
289
+ error("Unsupported language #{lang}. Zillabyte currently supports #{languages.join(', ')}.", type) if not languages.include? lang
407
290
 
408
291
  display "initializing empty #{lang} app in #{dir}" if type.nil?
409
- FileUtils.cp_r( File.expand_path("../templates/#{lang}", __FILE__) + "/." , dir )
292
+ erb_binding = binding
293
+ FileUtils.mkdir_p dir
294
+
295
+ Dir[File.join(File.expand_path("../templates/apps/#{lang}", __FILE__), "*")].each do |source_file|
296
+
297
+ next if File.directory?(source_file)
298
+ erb = ERB.new(File.read(source_file))
299
+ erb.filename = source_file
300
+
301
+ dest_file = File.join(dir, File.basename(source_file).gsub(/\.erb$/, ""))
302
+ File.open(dest_file, 'w') {|file| file.write(erb.result(erb_binding))}
303
+
304
+ end
410
305
 
411
306
 
412
307
  end
413
308
 
414
309
 
310
+ # apps:errors ID
311
+ #
312
+ # show recent errors generated by the app
313
+ # --output_type OUTPUT_TYPE # specify an output type i.e. json
314
+ #
315
+ def errors
316
+ super
317
+ end
415
318
 
416
319
  # apps:logs ID [OPERATION_NAME]
417
320
  #
@@ -422,101 +325,9 @@ class Zillabyte::Command::Apps < Zillabyte::Command::Base
422
325
  # -v, --verbose LEVEL # sets the verbosity (error, info, debug) (default: info)
423
326
  #
424
327
  def logs
425
-
426
- app_id = options[:id] || shift_argument
427
- if app_id.nil?
428
- app_id = read_name_from_conf(options)
429
- options[:is_name] = true
430
- elsif !(app_id =~ /^\d*$/)
431
- options[:is_name] = true
432
- end
433
-
434
- operation_id = options[:operation] || shift_argument || '_ALL_'
435
- category = options[:verbose] || '_ALL_'
436
- type = options[:output_type]
437
-
438
- carry_settings = {
439
- :category => category
440
- }
441
-
442
- display "Retrieving logs for app ##{app_id}...please wait..." if type.nil?
443
- hash = self.api.logs.get(app_id, operation_id, options)
444
-
445
- fetch_logs(hash, operation_id)
446
-
328
+ super
447
329
  end
448
- alias_command "logs", "apps:logs"
449
-
450
-
451
-
452
- # apps:errors ID
453
- #
454
- # show recent errors generated by the app
455
- # --output_type OUTPUT_TYPE # specify an output type i.e. json
456
- #
457
- def errors
458
-
459
- # Init
460
- app_id = options[:id] || shift_argument
461
-
462
- # No name?
463
- if app_id.nil?
464
- app_id = read_name_from_conf(options)
465
- options[:is_name] = true
466
- elsif !(app_id =~ /^\d*$/)
467
- options[:is_name] = true
468
- end
469
-
470
- type = options[:output_type]
471
-
472
- # Make the request
473
- res = api.request(
474
- :expects => 200,
475
- :method => :get,
476
- :body => options.to_json,
477
- :path => "/flows/#{CGI.escape(app_id)}/errors"
478
- )
479
-
480
- # Render
481
- display "Recent errors:" if type.nil?
482
- headings = ["operation", "date", "error"]
483
- rows = (res.body["recent_errors"] || []).map do |row|
484
- if row['date']
485
- d = Time.at(row['date']/1000)
486
- else
487
- d = nil
488
- end
489
- [row['name'], d, row['message']]
490
- end
491
- rows.sort! do |a,b|
492
- a[1] <=> b[1]
493
- end
494
- color_map = {}
495
- colors = LogFormatter::COLORS.clone
496
- rows.each do |row|
497
- name = row[0]
498
- time = row[1]
499
- message = row[2].strip
500
- color_map[name] ||= colors.shift
501
- if time
502
- display "#{"* #{name} - #{time_ago_in_words(time)} ago".colorize(color_map[name])}:" if type.nil?
503
- else
504
- display "#{"* #{name}".colorize(color_map[name])}:" if type.nil?
505
- end
506
- message.split('\n').each do |sub_line|
507
- display " #{sub_line}" if type.nil?
508
- end
509
- end
510
-
511
- end
512
-
513
-
514
-
515
-
516
330
 
517
-
518
-
519
-
520
331
 
521
332
  # apps:cycles ID [OPTIONS]
522
333
  #
@@ -565,11 +376,11 @@ class Zillabyte::Command::Apps < Zillabyte::Command::Base
565
376
 
566
377
  if response["job_id"]
567
378
  options[:job_id] = response["job_id"]
568
- app_id = response["flow_id"]
379
+ app_id = response["app_id"]
569
380
  options.delete :is_name
570
381
 
571
382
  start = Time.now.utc
572
- display "Next cycle request sent. If your app was RETIRED this may take slightly longer." if type.nil?
383
+ display "Next cycle request sent. If your app was RETIRED this may take a few minutes. Check 'zillabyte logs' for progress." if type.nil?
573
384
 
574
385
  while(Time.now.utc < start + MAX_POLL_SECONDS) do
575
386
 
@@ -605,475 +416,19 @@ class Zillabyte::Command::Apps < Zillabyte::Command::Base
605
416
  end
606
417
 
607
418
 
608
- # apps:test [DIR]
419
+ # apps:test
609
420
  #
610
421
  # tests a local app with sample data
611
422
  #
612
- # --config CONFIG_FILE # use the given config file
613
- # --output OUTPUT_FILE # writes sink output to a file
614
- # --wait MAX # max time to spend on each operation (default 10 seconds)
615
- # --batches BATCHES # number of batches to emit (default 1)
616
- # --directory DIR # app directory
423
+ # --config CONFIG_FILE # use the given config file
424
+ # --output OUTPUT_FILE # writes sink output to a file
425
+ # --cycles CYCLES # number of cycles to emit (default 1)
426
+ # --directory DIR # app directory
427
+ # -i, --interactive # allow user to control input and read output
617
428
  #
618
429
  def test
619
-
620
- output = options[:output]
621
- otype = options[:output_type] #type is used below for something else
622
-
623
- max_seconds = (options[:wait] || "30").to_i
624
- batches = (options[:batches] || "1").to_i
625
-
626
- def read_message(read_stream, color)
627
- msg = nil
628
- read_stream.each do |line|
629
- line.strip!
630
- if(line == "end")
631
- return msg
632
- end
633
- begin
634
- hash = JSON.parse(line)
635
- if(hash["command"] == "done")
636
- msg = "done"
637
- else
638
- msg = hash.to_json
639
- end
640
- rescue
641
- next
642
- end
643
- end
644
- msg
645
- end
646
-
647
- def write_message(write_stream, msg)
648
- write_stream.write msg.strip + "\n"
649
- write_stream.write "end\n"
650
- write_stream.flush
651
- end
652
-
653
- def truncate_message(msg)
654
- return msg if(!msg.instance_of?(String))
655
- t_length = 50 # truncates entries to this length
656
- m_length = msg.length
657
- msg_out = m_length > t_length ? msg[0..t_length-3]+"..." : msg
658
- msg_out
659
- end
660
-
661
- def handshake(write_stream, read_stream, node, color)
662
- begin
663
- write_message write_stream, "{\"pidDir\": \"/tmp\"}\n"
664
- read_message read_stream, color # Read to "end\n"
665
- rescue Exception => e
666
- puts "Error handshaking node: #{node}"
667
- raise e
668
- end
669
- end
670
-
671
-
672
-
673
- # INIT
674
- dir = options[:directory] || shift_argument
675
- if dir.nil?
676
- dir = Dir.pwd
677
- else
678
- dir = File.expand_path(dir)
679
- end
680
- options[:directory] = dir
681
-
682
- meta = Zillabyte::API::Apps.get_rich_meta_info_from_script(dir, self, {:test => true})
683
- if meta.nil?
684
- error "this is not a valid zillabyte app directory"
685
- exit
686
- end
687
-
688
- # Show the user what we know about their app...
689
- display "inferring your app details..."
690
- colors = {}
691
- describe_app(meta, colors)
692
-
693
-
694
- # Extract the app's information..
695
- nodes = meta["nodes"]
696
- write_to_next_each = []
697
- write_queue = []
698
- stream_messages = {}
699
- default_stream = "_default"
700
-
701
- # Iterate all nodes sequentially and invoke them in separate processes...
702
- nodes.each do |node|
703
-
704
- # Init
705
- type = node["type"]
706
- name = node["name"]
707
- consumes = node["consumes"]
708
- emits = node["emits"]
709
-
710
- color = colors[name] || :default
711
-
712
- op_display = lambda do |msg, override_color = nil|
713
- display "#{name} - #{msg}".colorize(override_color || color)
714
- end
715
-
716
- # A Source?
717
- if type == "source"
718
-
719
- # A source from relation?
720
- if node['matches'] or node["relation"]
721
- matches = node['matches'] || (node["relation"]["query"])
722
- emits = emits.first #For spouting from a relation, there should only be one emits
723
- op_display.call "Grabbing remote data"
724
-
725
- res = api.query.agnostic(matches)
726
- rows = res["rows"]
727
- column_aliases = res["column_aliases"]
728
-
729
-
730
- if(rows.nil? or rows.length == 0)
731
- raise NameError, "Could not find data that matches your 'matches' clause"
732
- end
733
- rows.each do |tuple|
734
- values = {}
735
- meta = {}
736
- tuple.each do |k, v|
737
- if(k == "id")
738
- next
739
- elsif(k == "confidence" or k == "since" or k == "source")
740
- meta[k] = v
741
- else
742
- values[k] = v
743
- end
744
- end
745
- read_msg = {"tuple" => values, "meta" => meta, "column_aliases" => column_aliases}.to_json
746
- values = Hash[values.map{|k, v| [truncate_message(k), truncate_message(v)]}]
747
- op_display.call "emitted: #{values} #{meta} to #{emits}"
748
- stream_messages[emits] ||= []
749
- stream_messages[emits] << read_msg
750
- end
751
-
752
- # Done processing...
753
- next
754
-
755
- else
756
-
757
- # A regular source..
758
- stream_messages[default_stream] ||= []
759
- stream_messages[default_stream] << "{\"command\": \"prepare\"}\n"
760
- stream_messages[default_stream] << "{\"command\": \"begin_cycle\"}\n"
761
- emits.each {|ss| stream_messages[ss] = []} #initialize streams
762
- stream_size_at_last_call_to_next_tuple = Hash[emits.map {|ss| [ss, 0]}] #initialize initial size of streams (all 0)
763
- # the above initializations are used to deal with the case where end_cycle_policy == "null_emit"
764
- n_batches_emitted = 1
765
- end_cycle_received = false
766
- last_call_next_tuple = false
767
-
768
- end
769
-
770
- # An Aggregate?
771
- elsif type == "aggregate"
772
- if node['consumes']
773
- input_stream = node['consumes']
774
- else
775
- input_stream = stream_messages.keys.first
776
- end
777
- messages = stream_messages[input_stream] || []
778
- stream_messages[input_stream] = []
779
-
780
- group_by = node['group_by']
781
- group_tuples = {}
782
- messages.each do |msg|
783
- msg = JSON.parse(msg)
784
- tuple = msg["tuple"].to_json
785
- meta = msg["meta"].to_json
786
- column_aliases = msg["column_aliases"] || {}
787
- aliases = Hash[column_aliases.map{|h| [h["alias"],h["concrete_name"]]}]
788
- gt = {}
789
- group_by.each do |field|
790
- field_name = aliases[field] || field
791
- gt[field] = msg["tuple"][field_name]
792
- end
793
-
794
- msg_no_brackets = "\"tuple\": #{tuple}, \"meta\": #{meta}, \"column_aliases\": #{column_aliases.to_json}"
795
- if group_tuples[gt]
796
- group_tuples[gt] << msg_no_brackets
797
- else
798
- group_tuples[gt] = [msg_no_brackets]
799
- end
800
- end
801
-
802
- group_tuples.each do |group_tuple, tuples|
803
- stream_messages[input_stream] << "{\"command\": \"begin_group\", \"tuple\": #{group_tuple.to_json}, \"meta\":{}}\n"
804
- tuples.each do |t|
805
- stream_messages[input_stream] << "{\"command\": \"aggregate\", #{t}}\n"
806
- end
807
- stream_messages[input_stream] << "{\"command\": \"end_group\"}\n"
808
- end
809
-
810
- # A Sink?
811
- elsif type == "sink"
812
-
813
- if consumes.nil?
814
- error "The node #{name} must declare which stream it 'consumes'"
815
- end
816
- messages = stream_messages[consumes] || []
817
-
818
- table = Terminal::Table.new :title => name
819
- csv_str = CSV.generate do |csv|
820
- header_written = false;
821
- messages.each do |msg|
822
- obj = JSON.parse(msg)
823
- t = obj['tuple']
824
- m = obj['meta'] || {}
825
- if t
826
- if header_written == false
827
- keys = [t.keys, m.keys].flatten
828
- csv << keys
829
- table << keys
830
- table << :separator
831
- header_written = true
832
- end
833
- vals = [t.values, m.values].flatten
834
- csv << vals
835
- table << vals
836
- end
837
- end
838
- end
839
-
840
- display table.to_s.colorize(color)
841
-
842
- if output
843
- filename = "#{output}.csv"
844
- f = File.open(filename, "w")
845
- f.write(csv_str)
846
- f.close()
847
- op_display.call "output written to #{filename}"
848
- end
849
-
850
- next
851
- end
852
-
853
-
854
- cmd = command("--execute_live --name #{name}", otype, dir)
855
- begin
856
-
857
- # Start the operation...
858
- op_display.call "beginning #{type} #{name}"
859
- Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thread|
860
- begin
861
-
862
- # Init
863
- handshake stdin, stdout, node, color
864
- write_queue = []
865
- read_queue = []
866
- mutex = Mutex.new
867
- signal = ConditionVariable.new
868
-
869
- if consumes.nil?
870
- # Assume default stream (this should only happen for the source)
871
- stream_name = default_stream
872
- else
873
- stream_name = consumes
874
- end
875
- write_queue = stream_messages[stream_name].clone
876
- stream_messages.delete(stream_name)
877
-
878
- # Start writing the messages...
879
- writing_thread = Thread.start do
880
-
881
- while(true)
882
-
883
- case type
884
- when 'source'
885
- break if n_batches_emitted > batches
886
- if write_queue.empty?
887
- sleep 0.5
888
- next
889
- end
890
- else
891
- break if write_queue.empty?
892
- end
893
-
894
- # Make sure we're not reading anything...
895
- while(write_queue.size == 0)
896
- mutex.synchronize do
897
- signal.wait(mutex)
898
- end
899
- end
900
-
901
- # Get next mesage
902
- write_msg = write_queue.shift
903
-
904
- # Make it human-understable
905
- write_json = JSON.parse(write_msg)
906
- if write_json['tuple']
907
- display_hash = Hash[write_json['tuple'].map{|k, v| [truncate_message(k), truncate_message(v)]}]
908
- op_display.call "receiving: #{display_hash}"
909
- elsif write_json['command'] == 'next'
910
- last_call_next_tuple = true
911
- op_display.call "asking for next tuple(s)"
912
- else
913
- # puts write_json
914
- end
915
-
916
- # Actually send it to the process
917
- begin
918
- write_message stdin, write_msg
919
- sleep 0.1
920
- rescue Exception => e
921
- puts "Error running #{cmd}: #{e}"
922
- raise e
923
- end
924
- end
925
- end
926
-
927
- # Start reading messages...
928
- reading_thread = Thread.start do
929
- while(true)
930
-
931
- # If the end cycle command is received, we either trigger the next cycle if the number of emitted
932
- # cycles is less than what the user requested, or we break
933
- if type == "source" and end_cycle_received
934
-
935
- mutex.synchronize do
936
- write_queue << "{\"command\": \"begin_cycle\"}\n"
937
- signal.signal()
938
- end
939
-
940
- n_batches_emitted += 1
941
- end_cycle_received = false
942
- last_call_next_tuple = false
943
- break if n_batches_emitted > batches
944
- sleep 0.5
945
- next
946
- end
947
-
948
- # Get next message
949
- read_msg = read_message(stdout, color);
950
-
951
- if read_msg == "done" || read_msg.nil?
952
-
953
- # For sources, if we receive a "done", check to see if any of the streams emitted by the source has
954
- # increased in size since the last call to next_tuple. If so, the cycle isn't over, otherwise, the
955
- # current call to next_tuple emitted nothing and if the end_cycle_policy is set to null_emit, this
956
- # should end the current cycle.
957
-
958
- if type == "source"
959
- if last_call_next_tuple and node["end_cycle_policy"] == "null_emit"
960
- end_cycle_received = true
961
- emits.each do |ss|
962
- end_cycle_received = false if stream_messages[ss].size > stream_size_at_last_call_to_next_tuple[ss]
963
- break
964
- end
965
- next if end_cycle_received
966
- end
967
-
968
- # If the policy isn't "null_emit", then just request next_tuple again
969
- mutex.synchronize do
970
- write_queue << "{\"command\": \"next\"}\n"
971
- signal.signal()
972
- end
973
-
974
- end
975
-
976
- # For other operations, if the queue is empty then we're done
977
- if write_queue.empty?
978
- break # exit while loop
979
- else
980
- sleep 0.5 # spin wait
981
- next
982
- end
983
- end
984
-
985
- # Process message
986
- obj = JSON.parse(read_msg)
987
-
988
- # process the received tuple or other commands
989
- if obj['tuple']
990
-
991
- # if
992
- tt = obj['tuple']
993
- tt.each do |kk, vv|
994
- if tt[kk].nil? and type == "source" and node["end_cycle_policy"] == "null_emit"
995
- end_cycle_received = true
996
- # read rest of stuff in stdout buffer until "done"
997
- mm = nil
998
- while(mm != "done")
999
- mm = read_message(stdout, color)
1000
- end
1001
- break
1002
- end
1003
- end
1004
- next if end_cycle_received
1005
-
1006
- # Convert to a incoming tuple for the next operation
1007
- next_msg = {
1008
- :tuple => obj['tuple'],
1009
- :meta => obj['meta']
1010
- }
1011
- emit_stream = obj['stream']
1012
- stream_messages[emit_stream] ||= []
1013
- stream_messages[emit_stream] << next_msg.to_json
1014
-
1015
- display_hash = Hash[obj['tuple'].map{|k, v| [truncate_message(k), truncate_message(v)]}]
1016
- op_display.call "emitted: #{display_hash} to #{emit_stream}"
1017
-
1018
- # track stream message size to end cycles when necessary
1019
- stream_size_at_last_call_to_next_tuple[emit_stream] = stream_messages[emit_stream].size if type == "source"
1020
- elsif obj['command'] == 'end_cycle'
1021
- end_cycle_received = true
1022
- # command:end_cycle should always be followed by done, read it (below) so
1023
- # that it doesn't interfere with next
1024
- read_message(stdout, color)
1025
- elsif obj['command'] == 'log'
1026
- op_display.call "log: #{obj['msg']}"
1027
- elsif obj['command'] == 'fail'
1028
- op_display.call "error: #{obj['msg']}", :red
1029
- exit(1)
1030
- elsif obj['ping']
1031
-
1032
- mutex.synchronize do
1033
- write_queue << "{\"pong\": \"#{Time.now.utc.to_f}\"}\n"
1034
- signal.signal()
1035
- end
1036
-
1037
- else
1038
- error "unknown message: #{read_msg}"
1039
- end
1040
-
1041
- end
1042
- end
1043
-
1044
- # stderr thread
1045
- stderr_thread = Thread.start do
1046
- stderr.each do |line|
1047
- op_display.call("stderr: #{line}", :red)
1048
- end
1049
- end
1050
-
1051
- begin
1052
- killed = Timeout.timeout(max_seconds) do
1053
- reading_thread.join()
1054
- writing_thread.join()
1055
- stderr_thread.kill()
1056
- op_display.call "completed #{type} #{name}"
1057
- end
1058
- rescue Timeout::Error
1059
- op_display.call "max time reached. preempting #{type} #{name}. set --wait to increase", :red
1060
- reading_thread.kill() if reading_thread.alive?
1061
- writing_thread.kill() if writing_thread.alive?
1062
- stderr_thread.kill() if stderr_thread.alive?
1063
- end
1064
-
1065
- rescue Errno::EIO
1066
- puts "Errno:EIO error, but this probably just means " +
1067
- "that the process has finished giving output"
1068
- end
1069
- end
1070
- rescue PTY::ChildExited
1071
- puts "The child process exited!"
1072
- end
1073
- end
1074
-
430
+ super
1075
431
  end
1076
- alias_command "test", "apps:test"
1077
432
 
1078
433
 
1079
434
  # apps:kill ID
@@ -1084,7 +439,6 @@ class Zillabyte::Command::Apps < Zillabyte::Command::Base
1084
439
  # --output_type OUTPUT_TYPE # specify an output type i.e. json
1085
440
  #
1086
441
  def kill
1087
-
1088
442
  id = options[:id] || shift_argument
1089
443
  type = options[:output_type]
1090
444
 
@@ -1103,7 +457,6 @@ class Zillabyte::Command::Apps < Zillabyte::Command::Base
1103
457
  else
1104
458
  display "App ##{id} killed"
1105
459
  end
1106
-
1107
460
  end
1108
461
 
1109
462
 
@@ -1117,32 +470,8 @@ class Zillabyte::Command::Apps < Zillabyte::Command::Base
1117
470
  #
1118
471
  # HIDDEN:
1119
472
  def live_run
1120
-
1121
- name = options[:name] || shift_argument
1122
- type = options[:output_type]
1123
-
1124
- thread_id = options[:thread] || shift_argument || ""
1125
- dir = options[:directory] || shift_argument
1126
- if dir.nil?
1127
- dir = Dir.pwd
1128
- else
1129
- dir = File.expand_path(dir)
1130
- end
1131
- options[:directory] = dir
1132
-
1133
- meta = Zillabyte::CLI::Config.get_config_info(dir, options)
1134
-
1135
- if meta.nil?
1136
- error("could not find meta information for: #{dir}", type)
1137
- end
1138
-
1139
- if(thread_id == "")
1140
- exec(command("--execute_live --name #{name.to_s}",type, dir))
1141
- else
1142
- exec(command("--execute_live --name #{name.to_s} --pipe #{dir}/#{thread_id}",type, dir))
1143
- end
473
+ super
1144
474
  end
1145
- alias_command "live_run", "apps:live_run"
1146
475
 
1147
476
 
1148
477
 
@@ -1154,36 +483,11 @@ class Zillabyte::Command::Apps < Zillabyte::Command::Base
1154
483
  # --directory DIR # Directory of the app
1155
484
  #
1156
485
  def status
1157
-
1158
- id = options[:id] || shift_argument
1159
- type = options[:output_type]
1160
-
1161
- if id.nil?
1162
- id = read_name_from_conf(options)
1163
- options[:is_name] = true
1164
- elsif !(id =~ /^\d*$/)
1165
- options[:is_name] = true
1166
- end
1167
-
1168
- res = api.request(
1169
- :expects => 200,
1170
- :method => :get,
1171
- :path => "/flows/#{id}"
1172
- )
1173
- res.body
1174
-
1175
- if type == "json"
1176
- display res.body.to_json
1177
- else
1178
- # TODO
1179
- display res.body.to_json
1180
- end
1181
-
486
+ super
1182
487
  end
1183
488
 
1184
489
 
1185
490
 
1186
-
1187
491
  # apps:info [DIR]
1188
492
  #
1189
493
  # outputs the info for the app in the dir.
@@ -1193,116 +497,13 @@ class Zillabyte::Command::Apps < Zillabyte::Command::Base
1193
497
  # --directory DIR # Directory of the app
1194
498
  #
1195
499
  def info
1196
-
1197
- dir = options[:directory] || shift_argument
1198
- if dir.nil?
1199
- dir = Dir.pwd
1200
- else
1201
- dir = File.expand_path(dir)
1202
- end
1203
- options[:directory] = dir
1204
-
1205
- info_file = "#{dir}/#{SecureRandom.uuid}"
1206
- type = options[:output_type]
1207
-
1208
- cmd = command("--info --file #{info_file}", type, dir)
1209
- app_info = Zillabyte::Command::Apps.get_info(cmd, info_file, dir, options)
1210
-
1211
- if type == "json"
1212
- puts app_info
1213
- else
1214
- if options[:pretty]
1215
- puts JSON.pretty_generate(JSON.parse(app_info))
1216
- else
1217
- puts app_info
1218
- end
1219
- end
1220
-
1221
- exit
500
+ super
1222
501
  end
1223
- alias_command "info", "apps:info"
1224
-
1225
-
1226
- #
1227
- # --output_type OUTPUT_TYPE # specify an output type i.e. json
1228
- #
1229
- def self.get_info(cmd, info_file, dir, options = {})
1230
- type = options[:output_type]
1231
502
 
1232
- response = `#{cmd}`
1233
- if($?.exitstatus == 1)
1234
-
1235
- File.delete("#{info_file}") if File.exists?(info_file)
1236
-
1237
- if options[:output_type].nil?
1238
- exit(1)
1239
- else
1240
- Zillabyte::Helpers.error("error: #{response}", type)
1241
- end
1242
- end
1243
-
1244
- app_info = {}
1245
- File.open("#{info_file}", "r+").each do |line|
1246
- line = JSON.parse(line)
1247
- if(line["type"])
1248
- app_info["nodes"] << line
1249
- else
1250
- app_info = line
1251
- app_info["nodes"] = []
1252
- end
1253
- end
1254
- File.delete("#{info_file}")
1255
-
1256
- app_info = app_info.to_json
1257
- if(File.exists?("#{dir}/info_to_java.in"))
1258
- java_pipe = open("#{dir}/info_to_java.in","w+")
1259
- java_pipe.puts(app_info+"\n")
1260
- java_pipe.flush
1261
- java_pipe.close()
1262
- end
1263
-
1264
- app_info
1265
- end
1266
503
 
1267
504
 
1268
505
  private
1269
-
1270
- #
1271
- # --output_type OUTPUT_TYPE # specify an output type i.e. json
1272
- #
1273
- def command(arg="--execute_live", type = nil, dir = Dir.pwd, ignore_stderr = false)
1274
- meta = Zillabyte::CLI::Config.get_config_info(dir, self, options)
1275
-
1276
- #meta = Zillabyte::API::Functions.get_rich_meta_info_from_script(dir, self)
1277
- error("could not extract meta information. missing zillabyte.conf.yml?", type) if meta.nil?
1278
- error(meta["error_message"], type) if meta['status'] == "error"
1279
- full_script = File.join(dir, meta["script"])
1280
- stderr_opt = "2> /dev/null" if ignore_stderr
1281
-
1282
- case meta["language"]
1283
- when "ruby"
1284
- # Execute in the bundler context
1285
- cmd = "cd \"#{dir}\"; unset BUNDLE_GEMFILE; ZILLABYTE_HARNESS=1 bundle exec ruby \"#{full_script}\" #{arg} #{stderr_opt}"
1286
-
1287
- when "python"#{
1288
- if(File.directory?("#{dir}/vEnv"))
1289
- cmd = "cd \"#{dir}\"; PYTHONPATH=~/zb1/multilang/python/Zillabyte #{dir}/vEnv/bin/python \"#{full_script}\" #{arg} #{stderr_opt}"
1290
- else
1291
- cmd = "cd \"#{dir}\"; PYTHONPATH=~/zb1/multilang/python/Zillabyte python \"#{full_script}\" #{arg} #{stderr_opt}"
1292
- end
1293
-
1294
- when "js"
1295
- # cmd = "#{Zillabyte::API::CASPERJS_BIN} #{Zillabyte::API::API_CLIENT_JS} \"#{full_script}\" #{arg}"
1296
- cmd = "cd \"#{dir}\"; NODE_PATH=~/zb1/multilang/js/src/lib #{Zillabyte::API::NODEJS_BIN} \"#{full_script}\" #{arg} #{stderr_opt}"
1297
-
1298
- else
1299
- error("no language specified", type)
1300
- end
1301
-
1302
- return cmd
1303
506
 
1304
- end
1305
-
1306
507
  #
1307
508
  #
1308
509
  #
@@ -1324,46 +525,4 @@ class Zillabyte::Command::Apps < Zillabyte::Command::Base
1324
525
 
1325
526
  end
1326
527
 
1327
- #
1328
- #
1329
- def read_name_from_conf(options = {})
1330
- type = options[:output_type]
1331
- hash = Zillabyte::API::Apps.get_rich_meta_info_from_script Dir.pwd, options
1332
- error("No id given and current directory does not contain a valid Zillabyte configuration file. Please specify an app id or run command from the directory containing the app.",type) if hash["error"]
1333
- hash["name"]
1334
- end
1335
-
1336
- def fetch_logs(hash, operation=nil, exit_on=nil)
1337
- def color_for(operation_name)
1338
- @color_map[operation_name] ||= @all_colors[ @color_map.size % @all_colors.size ]
1339
- @color_map[operation_name]
1340
- end
1341
-
1342
- @color_map = {}
1343
- @all_colors = [:green, :yellow, :magenta, :cyan, :light_black, :light_green, :light_yellow, :light_blue, :light_magenta, :light_cyan]
1344
- if(hash["server"] == "localhost")
1345
- cmd = "tail -n 500 -f /tmp/flows/f#{hash["app"]}/flow_logs/app_#{hash["app"]}.log"
1346
- else
1347
- cmd = "curl -G http://#{hash["app"]}:#{hash["token"]}@#{hash["server"]}:#{hash["port"]}/getLogs?app=#{hash["app"]}"
1348
- end
1349
-
1350
- begin
1351
- PTY.spawn( cmd ) do |r, w, pid|
1352
- r.each do |line|
1353
- begin
1354
- op = line.match(/\[(.*?)\]/)[0].to_s[1...-1]
1355
- op_name = op.split(".")[0]
1356
- next if op_name != operation and operation != "_ALL_"
1357
- line_split = line.split("[")
1358
- print line_split[0] + "[" + op.colorize(color_for(op)) + line_split[1..-1].join("[")[op.length..-1]
1359
- break if exit_on and line.include? exit_on
1360
- rescue Exception => e
1361
- next
1362
- end
1363
- end
1364
- end
1365
- rescue PTY::ChildExited => e
1366
- end
1367
- end
1368
-
1369
528
  end