zillabyte-cli 0.0.24 → 0.1.0

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