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
@@ -0,0 +1,177 @@
1
+ require "zillabyte/cli/base"
2
+ require "zillabyte/cli/helpers/table_output_builder"
3
+ require "cgi"
4
+ require "csv"
5
+ require "open-uri"
6
+ require "json"
7
+
8
+
9
+ # manage authentication keys
10
+ #
11
+ class Zillabyte::Command::Keys < Zillabyte::Command::Base
12
+
13
+ SHORT_KEY_SPLIT = 18
14
+ SHORT_KEY_MAX = 64
15
+
16
+ # keys:add NAME [KEY]
17
+ #
18
+ # add a key for the current user
19
+ #
20
+ # if no KEY is specified, will try to find ~/.ssh/id_rsa.pub
21
+ #
22
+ # --output_type OUTPUT_TYPE # The output format type
23
+ #
24
+ def add()
25
+ type = options[:output_type] || nil
26
+ name = shift_argument()
27
+ if name.nil?
28
+ error("no name given", type)
29
+ end
30
+ keypath = shift_argument() || "~/.ssh/id_rsa.pub"
31
+ begin
32
+ key = File.binread(File.expand_path(keypath))
33
+ rescue => e
34
+ error(e.message, type)
35
+ end
36
+ message = api.keys.add(name, key)
37
+ if type == "json"
38
+ display("{}")
39
+ else
40
+ display(message)
41
+ end
42
+ end
43
+
44
+ # keys:show NAME
45
+ #
46
+ # show a key for the current user
47
+ #
48
+ # --output_type OUTPUT_TYPE # The output format type
49
+ #
50
+ def show()
51
+ type = options[:output_type] || nil
52
+ name = shift_argument()
53
+ if name.nil?()
54
+ error("no name given", type)
55
+ end
56
+
57
+ key = api.keys.show(name)
58
+ if type == "json"
59
+ display({"key" => key}.to_json())
60
+ else
61
+ display(key)
62
+ end
63
+ end
64
+
65
+ # keys:remove [NAME]
66
+ #
67
+ # remove a key from the current user
68
+ #
69
+ # --output_type OUTPUT_TYPE # The output format type
70
+ # -l, --long # display extended information for each key
71
+ # -f, --force # Force -- remove key without prompting
72
+ #
73
+ def remove()
74
+ type = options[:output_type] || nil
75
+ long = options[:long]
76
+ force = options[:force]
77
+ name = shift_argument()
78
+ key_data = if name.nil?()
79
+ if force
80
+ error("no name given", type)
81
+ end
82
+ keys = api.keys.list()
83
+ display("=== Keys")
84
+ display(format_keys(keys, long, nil))
85
+ # NOTE: it may not be the most efficient thing to use a hash here, unless
86
+ # we expect users to routinely try to remove invalid keys.
87
+ keymap = Hash[*keys.flatten()]
88
+ loop do
89
+ display("Please enter the name of the key you wish to remove (case-sensitive):")
90
+ name = ask().chomp()
91
+ if name.empty?()
92
+ error("Aborting.", type)
93
+ end
94
+ if keymap.has_key?(name)
95
+ break(keymap[name])
96
+ end
97
+ display("Key not found: `#{name}`")
98
+ end
99
+ else
100
+ key = api.keys.show(name)
101
+ if force
102
+ key
103
+ else
104
+ display(format_keys([[name, key]], long, nil))
105
+ loop do
106
+ display("Proceed with removal? (yes or no)")
107
+ choice = ask().chomp()
108
+ if choice == "y" || choice == "yes"
109
+ break(key)
110
+ elsif choice == "n" || choice == "no"
111
+ error("Aborting.", type)
112
+ else
113
+ display("Invalid response.")
114
+ end
115
+ end
116
+ end
117
+ key
118
+ end
119
+
120
+ message = api.keys.remove(name, key_data)
121
+ if type == "json"
122
+ display("{}")
123
+ else
124
+ display(message)
125
+ end
126
+ end
127
+
128
+ # keys
129
+ #
130
+ # display keys for the current user
131
+ #
132
+ # --output_type OUTPUT_TYPE # The output format type
133
+ # -l, --long # display extended information for each key
134
+ def index()
135
+ type = options[:output_type] || nil
136
+ long = options[:long]
137
+
138
+ keys = api.keys.list()
139
+ if type.nil?()
140
+ display("=== Keys")
141
+ end
142
+ display(format_keys(keys, long, type))
143
+ end
144
+
145
+ # keys:clear
146
+ #
147
+ # remove all authentication keys from the current user
148
+ #
149
+ # --output_type OUTPUT_TYPE # The output format type
150
+ #
151
+ def clear()
152
+ type = options[:output_type] || nil
153
+
154
+ message = api.keys.clear()
155
+ if type == "json"
156
+ display("{}")
157
+ else
158
+ display(message)
159
+ end
160
+ end
161
+
162
+ private
163
+ def format_keys(keys, long, type)
164
+ outlong = long || type == "json"
165
+ rows = keys.map() { |row|
166
+ name = row[0]
167
+ key = row[1]
168
+ if outlong || key.length() < SHORT_KEY_MAX
169
+ [key, name]
170
+ else
171
+ ["#{key[0...SHORT_KEY_SPLIT]}...#{key[SHORT_KEY_SPLIT - SHORT_KEY_MAX + 2..-1]}", name]
172
+ end
173
+ }
174
+ headings = ["key", "name"]
175
+ TableOutputBuilder.build_table(headings, rows, type)
176
+ end
177
+ end
@@ -264,7 +264,6 @@ class Zillabyte::Command::Query < Zillabyte::Command::Base
264
264
  )
265
265
  res = res.body
266
266
 
267
- p res
268
267
  if(res["uri"])
269
268
  display "Waiting for download." if type.nil?
270
269
  File.open(file, "w") do |f|
@@ -520,6 +520,7 @@ class Zillabyte::Command::Relations < Zillabyte::Command::Base
520
520
  case filetype
521
521
  when "csv"
522
522
  begin
523
+
523
524
  CSV.foreach(file) do |row|
524
525
  if row.size != n_columns
525
526
  error("relation expect #{n_columns} column(s). Found a row with #{row.size}::\n #{row}", type)
@@ -539,7 +540,7 @@ class Zillabyte::Command::Relations < Zillabyte::Command::Base
539
540
  rows << row
540
541
  end
541
542
  rescue Exception => e
542
- error("unable to parse csv", type)
543
+ error("error when parsing csv : #{e}", type)
543
544
  end
544
545
 
545
546
  else
@@ -0,0 +1,17 @@
1
+ import zillabyte
2
+
3
+ def prep(controller):
4
+ return
5
+
6
+ # This is the heart of your algorithm. It's processed on every
7
+ # web page. This algorithm is run in parallel on possibly hundreds
8
+ # of machines.
9
+ def execute(controller, tup):
10
+ if("hello world" in tup["html"]):
11
+ controller.emit("has_hello_world",{"url":tup["url"]})
12
+ return
13
+
14
+ app = zillabyte.app(name = "hello_world")
15
+ app.source(matches = "select * from web_pages")\
16
+ .each(execute = execute)\
17
+ .sink(name = "has_hello_world", columns = [{"url":"string"}])
@@ -1,4 +1,4 @@
1
1
  language: python
2
- script: simple_function.py
2
+ script: app.py
3
3
  ignore_files:
4
4
  - ./vEnv
@@ -3,7 +3,7 @@
3
3
 
4
4
  require 'zillabyte'
5
5
 
6
- Zillabyte.app("hello_world_app")
6
+ Zillabyte.app("<%= name %>")
7
7
  .source("select * from web_pages")
8
8
  .each{ |page|
9
9
  if page['html'].include? "hello world"
@@ -7,18 +7,15 @@ def prep(controller):
7
7
  # web page. This algorithm is run in parallel on possibly hundreds
8
8
  # of machines.
9
9
  def execute(controller, tup):
10
- if("hello world" in tup["html"]):
11
- controller.emit("has_hello_world",{"url":tup["url"]})
10
+ if("hello world" in tup.values["html"]):
11
+ controller.emit("has_hello_world",{"url":tup.values["url"]})
12
12
  return
13
13
 
14
14
  zillabyte.simple_function(\
15
- # This specifies the function's name and is mandatory.
16
- name = "simple_function", \
17
-
18
15
  # This directive instructs zillabyte to give your function every
19
16
  # web page in our known universe. Your function will have access
20
17
  # to two fields: URL and HTML
21
- matches = "select * from web_pages", \
18
+ matches = "select * from web_pa", \
22
19
 
23
20
  # This directive tells Zillabyte what kind of data your function
24
21
  # produces. In this case, we're saying we will emit a tuple that
@@ -6,7 +6,9 @@ module Zillabyte::Common
6
6
  end
7
7
 
8
8
  def display(*args)
9
- puts *args
9
+ args.each do |arg|
10
+ STDOUT.syswrite(arg + "\n")
11
+ end
10
12
  end
11
13
 
12
14
  def error(*args)
@@ -7,7 +7,7 @@ module Zillabyte
7
7
 
8
8
  def display(msg="", new_line=true)
9
9
  if new_line
10
- puts(msg)
10
+ puts(msg)
11
11
  else
12
12
  print(msg)
13
13
  $stdout.flush
@@ -50,6 +50,69 @@ module Zillabyte
50
50
  # fails on windows
51
51
  end
52
52
  end
53
+
54
+ def has_git?
55
+ %x{ git --version }
56
+ $?.success?
57
+ end
58
+
59
+ def git(args)
60
+ return "" unless has_git?
61
+ flattened_args = [args].flatten.compact.join(" ")
62
+ %x{ git #{flattened_args} 2>&1 }.strip
63
+ end
64
+
65
+ def create_git_remote(remote, url)
66
+ return if git('remote').split("\n").include?(remote)
67
+ return unless File.exists?(".git")
68
+ git "remote add #{remote} #{url}"
69
+ display "Git remote #{remote} added"
70
+ end
71
+
72
+
73
+
74
+ def app
75
+ @app ||= if options[:confirm].is_a?(String)
76
+ if options[:app] && (options[:app] != options[:confirm])
77
+ error("Mismatch between --app and --confirm")
78
+ end
79
+ options[:confirm]
80
+ elsif options[:app].is_a?(String)
81
+ options[:app]
82
+ elsif ENV.has_key?('ZILLABYTE_APP')
83
+ ENV['ZILLABYTE_APP']
84
+ elsif app_from_dir = extract_app_in_dir(Dir.pwd)
85
+ app_from_dir
86
+ else
87
+ # raise instead of using error command to enable rescuing when app is optional
88
+ raise Zillabyte::Command::CommandFailed.new("No app specified.\nRun this command from an app folder or specify which app to use with --app APP.") unless options[:ignore_no_app]
89
+ end
90
+ end
91
+
92
+
93
+
94
+ def extract_app_in_dir(dir)
95
+ return unless remotes = git_remotes(dir)
96
+
97
+ if remote = options[:remote]
98
+ remotes[remote]
99
+ elsif remote = extract_app_from_git_config
100
+ remotes[remote]
101
+ else
102
+ apps = remotes.values.uniq
103
+ if apps.size == 1
104
+ apps.first
105
+ else
106
+ raise(Heroku::Command::CommandFailed, "Multiple apps in folder and no app specified.\nSpecify app with --app APP.") unless options[:ignore_no_app]
107
+ end
108
+ end
109
+ end
110
+
111
+ def extract_app_from_git_config
112
+ remote = git("config zillabyte.remote")
113
+ remote == "" ? nil : remote
114
+ end
115
+
53
116
 
54
117
  end
55
118
  end
@@ -0,0 +1,226 @@
1
+ require "zillabyte/cli/base"
2
+ require "zillabyte/runner"
3
+ require "zillabyte/runner/multilang_operation"
4
+ require 'thread'
5
+
6
+
7
+ # HIDDEN:
8
+ class Zillabyte::Runner::AppRunner < Zillabyte::Command::Base
9
+ include Zillabyte::Helpers
10
+
11
+ def run (dir = Dir.pwd, session = nil, options = {})
12
+
13
+ if session.nil?
14
+ return
15
+ end
16
+
17
+ @session = session
18
+ @colors = {}
19
+ output = options[:output]
20
+ otype = options[:output_type]
21
+ interactive = options[:interactive]
22
+ cycles = (options[:cycles] || "1").to_i
23
+
24
+ # Get app metadata
25
+ meta = Zillabyte::API::Flows.get_rich_meta_info_from_script(dir, @session, {:test => true})
26
+ if meta.nil? || meta["nodes"].nil?
27
+ error "this is not a valid zillabyte app directory"
28
+ exit
29
+ end
30
+
31
+ # Check that multilang version is atleast 0.1.0
32
+ version = meta["multilang_version"] || "0.0.0"
33
+ version_arr = version.split('.').map {|v| v.to_i}
34
+ if version_arr.empty? || (version_arr[0] == 0 && version_arr[1] < 1)
35
+ display "The version of zillabyte used in your application is outdated."
36
+ display "Please use upgrade your zillabyte gem via 'bundle update zillabyte; gem cleanup zillabyte'"
37
+ return
38
+ end
39
+
40
+ # Show the user what we know about their app...
41
+ display "inferring your app details..."
42
+ describe_app(meta)
43
+
44
+ # Setup streams
45
+ @nodes = meta["nodes"]
46
+
47
+ # Index stream consummers and emitters by stream name
48
+ @arcs = meta["arcs"]
49
+
50
+ # Organize component pipes
51
+ @operations = {}
52
+ @operation_pipes = {}
53
+
54
+ # On each cycle, setup and tear down a test harness
55
+ (1..cycles).each do |cycle|
56
+
57
+ display "starting cycle #{cycle}" unless cycles == 1
58
+
59
+ begin
60
+
61
+ # Setup component pipes
62
+ @nodes.each do |n|
63
+
64
+ name = n["name"]
65
+ type = n["type"]
66
+ emits = n["emits"]
67
+ if n["type"] == "source"
68
+ options[:end_cycle_policy] = n["end_cycle_policy"]
69
+ end
70
+
71
+ # Create two new pipes in the parent.
72
+ rd_child, wr_parent = IO.pipe()
73
+ rd_parent, wr_child = IO.pipe()
74
+
75
+ @operation_pipes[name] = {
76
+ :rd_child => rd_child,
77
+ :wr_child => wr_child,
78
+ :rd_parent => rd_parent,
79
+ :wr_parent => wr_parent
80
+ }
81
+ end
82
+
83
+ # Maps origin => {stream => [destinations]}
84
+ @arc_map = {}
85
+ @arcs.each do |a|
86
+ origin = a["origin"]
87
+ name = a["name"]
88
+ dest = a["dest"]
89
+ @arc_map[origin] ||= {}
90
+ @arc_map[origin][name] ||= []
91
+ @arc_map[origin][name] << a["dest"]
92
+ end
93
+
94
+ # Spawn component threads
95
+ @nodes.each do |n|
96
+
97
+ name = n["name"]
98
+ type = n["type"]
99
+ emits = n["emits"]
100
+
101
+ pipes = @operation_pipes[name]
102
+ rd_child = pipes[:rd_child]
103
+ wr_child = pipes[:wr_child]
104
+ rd_parent = pipes[:rd_parent]
105
+ wr_parent = pipes[:wr_parent]
106
+
107
+ # Fork.
108
+ pid = fork()
109
+ if pid # In parent
110
+ # Close the reading end of the child so we can write to the child.
111
+ rd_child.close()
112
+ # Close the writing end of the child so we can read from the child.
113
+ wr_child.close()
114
+
115
+ else # in child
116
+ # Close the writing end of the parent so we can read from the parent.
117
+ wr_parent.close()
118
+ # Close the reading end of the parent so we can write to the parent.
119
+ rd_parent.close()
120
+ begin
121
+
122
+
123
+ # Setup reading and writing pipes for communicating with consumee component
124
+ in_pipe = {:rd_child => @operation_pipes[name][:rd_child], :wr_child => @operation_pipes[name][:wr_child]}
125
+
126
+ # Index consumer pipes by stream name, consumer_name
127
+ out_pipes = {}
128
+
129
+
130
+ if type != "sink"
131
+ @arc_map[name].each_pair do |stream, destinations|
132
+ out_pipes[stream] ||= {}
133
+ destinations.each do |dest|
134
+ out_pipes[stream][dest] = {:wr_parent => @operation_pipes[dest][:wr_parent], :rd_parent => @operation_pipes[dest][:rd_parent] }
135
+ end
136
+ end
137
+ end
138
+
139
+ # Run the child process
140
+ Zillabyte::Runner::MultilangOperation.run(n, dir, in_pipe, out_pipes, self, meta, options)
141
+
142
+ ensure
143
+ # Close the reading end of the child
144
+ rd_child.close()
145
+ # Close the writing end of the child
146
+ wr_child.close()
147
+ exit!(-1)
148
+ end
149
+
150
+ end #end child
151
+ end
152
+
153
+ if interactive
154
+ display ""
155
+ while true
156
+ display "Enter an input tuple in JSON format i.e.{ \"url\" : \"foo.com\", \"html\" : \"bar.html\" }"
157
+ msg = ask
158
+
159
+ begin
160
+ JSON.parse(msg)
161
+ rescue JSON::ParserError
162
+ display "Received invalid JSON object"
163
+ next
164
+ end
165
+ # Send tuple to source
166
+ @operation_pipes["source_1"][:wr_parent].puts msg
167
+ end
168
+ end
169
+
170
+ ensure
171
+ Process.waitall()
172
+ @operation_pipes.each do |name, pipes|
173
+ #Close the writing end of the parent
174
+ pipes[:wr_parent].close()
175
+ # Close the reading end of the parent
176
+ pipes[:rd_parent].close()
177
+ end
178
+ end
179
+
180
+ end
181
+ end
182
+
183
+ def session
184
+ @session
185
+ end
186
+
187
+
188
+ def cdisplay(name, message)
189
+
190
+ color = @colors[name] || :default
191
+ if message == ""
192
+ display ""
193
+ else
194
+ display "#{name} - #{message}".colorize(color)
195
+ end
196
+ end
197
+
198
+
199
+ def query_agnostic(query)
200
+ @session.api.query.agnostic(query)
201
+ end
202
+
203
+ def display(message, newline = true)
204
+ @session.display(message, newline)
205
+ end
206
+
207
+
208
+ def describe_app(meta)
209
+ colors ||= [:green, :yellow, :magenta, :cyan, :white, :blue, :light_yellow, :light_blue, :red, :light_magenta, :light_cyan]
210
+ rjust = 20
211
+ display "#{'app name'.rjust(rjust)}: #{meta['name']}"
212
+ display "#{'app language'.rjust(rjust)}: #{meta['language']}"
213
+ meta['nodes'].each_with_index do |node, index|
214
+ @colors[node['name']] ||= colors[index % colors.length]
215
+ color = @colors[node['name']]
216
+ display (("="*rjust + " operation ##{index}").colorize(color))
217
+ display "#{"name".rjust(rjust)}: #{node['name'].to_s.colorize(color)}"
218
+ display "#{"type".rjust(rjust)}: #{node['type'].to_s.colorize(color)}"
219
+ display "#{"matches".rjust(rjust)}: #{JSON.pretty_generate(node['matches']).indent(rjust+2).lstrip.colorize(color)}" if node['matches']
220
+ display "#{"consumes".rjust(rjust)}: #{node['consumes'].to_s.colorize(color)}" if node['consumes']
221
+ display "#{"emits".rjust(rjust)}: #{JSON.pretty_generate(node['emits']).indent(rjust+2).lstrip.colorize(color)}" if node['emits']
222
+ end
223
+ end
224
+
225
+
226
+ end