td 0.8.0 → 0.9.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.
data/ChangeLog CHANGED
@@ -1,4 +1,10 @@
1
1
 
2
+ == 2011-09-08 version 0.9.0
3
+
4
+ * Changed subcommands grammer: action-group -> group:action
5
+ * Fixed import subcommand to strip \n
6
+
7
+
2
8
  == 2011-08-21 version 0.8.0
3
9
 
4
10
  * Splits API libraries to td-client gem
@@ -1,5 +1,12 @@
1
1
  = Treasure Data command line tool
2
2
 
3
+ This CUI utility wraps the td-client-ruby (https://github.com/treasure-data/td-client-ruby),
4
+ the REST API for managing databases and jobs on the Treasure Data Cloud.
5
+
6
+ For more about Treasure Data, see <http://treasure-data.com/>.
7
+
8
+ For full documentation see <http://docs.treasure-data.com/>.
9
+
3
10
  = Getting Started
4
11
 
5
12
  Install td command as a gem.
@@ -10,9 +17,19 @@ See help message for details.
10
17
 
11
18
  > td
12
19
 
20
+ You need to authorize the account, before executing any other commands.
21
+
22
+ > td account
13
23
 
14
- == Copyright
24
+ = Sample Workflow
25
+
26
+ > td account -f # authorize an account
27
+ user: k@treasure-data.com
28
+ password: **********
29
+ > td database:create mydb # create a database
30
+ > td table:create mydb www_access # create a table
31
+
32
+ = Copyright
15
33
 
16
34
  Copyright:: Copyright (c) 2011 Treasure Data Inc.
17
35
  License:: Apache License, Version 2.0
18
-
data/bin/td CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # -*- coding: utf-8 -*-
3
- #require 'rubygems' unless defined?(gem)
3
+ require 'rubygems' unless defined?(gem)
4
+ gem 'td-client'
4
5
  here = File.dirname(__FILE__)
5
6
  $LOAD_PATH << File.expand_path(File.join(here, '..', 'lib'))
6
7
  require 'td/command/td'
@@ -9,62 +9,10 @@ autoload :Schema, 'td/client'
9
9
  autoload :Job, 'td/client'
10
10
 
11
11
  module Command
12
- private
13
- def cmd_opt(name, *args)
14
- if args.last.to_s =~ /_$/
15
- multi = true
16
- args.push args.pop.to_s[0..-2]+'...'
17
- elsif args.last.to_s =~ /_\?$/
18
- multi = true
19
- args.push args.pop.to_s[0..-3]+'...?'
20
- end
21
-
22
- req_args, opt_args = args.partition {|a| a.to_s !~ /\?$/ }
23
- opt_args = opt_args.map {|a| a.to_s[0..-2].to_sym }
24
- args = req_args + opt_args
25
-
26
- args_line = req_args.map {|a| "<#{a}>" }
27
- args_line.concat opt_args.map {|a| "[#{a}]" }
28
- args_line = args_line.join(' ')
29
-
30
- description = List.get_description(name)
31
12
 
32
- op = OptionParser.new
33
- op.summary_indent = " "
34
- op.banner = <<EOF
35
- usage: #{$prog} #{name} #{args_line}
36
-
37
- description:
38
- #{description.split("\n").map {|l| " #{l}" }.join("\n")}
39
- EOF
40
-
41
- (class<<op;self;end).module_eval do
42
- define_method(:cmd_usage) do |msg|
43
- puts op.to_s
44
- puts ""
45
- puts "error: #{msg}" if msg
46
- exit 1
47
- end
48
-
49
- define_method(:cmd_parse) do
50
- begin
51
- parse!(ARGV)
52
- if ARGV.length < req_args.length || (!multi && ARGV.length > args.length)
53
- cmd_usage nil
54
- end
55
- if ARGV.length <= 1
56
- ARGV[0]
57
- else
58
- ARGV
59
- end
60
- rescue
61
- cmd_usage $!
62
- end
63
- end
64
-
65
- end
66
-
67
- op
13
+ private
14
+ def get_option(name)
15
+ List.get_option(name)
68
16
  end
69
17
 
70
18
  def get_client
@@ -95,32 +43,41 @@ EOF
95
43
  end
96
44
  end
97
45
 
98
- def find_database(client, db_name)
46
+ def get_database(client, db_name)
99
47
  begin
100
48
  return client.database(db_name)
101
49
  rescue
102
50
  cmd_debug_error $!
103
51
  $stderr.puts $!
104
- $stderr.puts "Use '#{$prog} show-databases' to show the list of databases."
52
+ $stderr.puts "Use '#{$prog} database:list' to show the list of databases."
105
53
  exit 1
106
54
  end
107
55
  db
108
56
  end
109
57
 
110
- def find_table(client, db_name, table_name, type=nil)
111
- db = find_database(client, db_name)
58
+ def parse_table_ident(table_ident)
59
+ db_name, table_name = table_ident.split('.', 2)
60
+ unless table_name
61
+ $stderr.puts "Invalid table identifier '#{table_ident}'; expected 'DB.TABLE'"
62
+ exit 1
63
+ end
64
+ return db_name, table_name
65
+ end
66
+
67
+ def get_table(client, db_name, table_name)
68
+ db = get_database(client, db_name)
112
69
  begin
113
70
  table = db.table(table_name)
114
71
  rescue
115
72
  $stderr.puts $!
116
- $stderr.puts "Use '#{$prog} show-tables #{db_name}' to show the list of tables."
73
+ $stderr.puts "Use '#{$prog} table:list #{db_name}' to show the list of tables."
117
74
  exit 1
118
75
  end
119
- if type && table.type != type
120
- $stderr.puts "Table '#{db_name}.#{table_name} is not a #{type} table but a #{table.type} table"
121
- end
76
+ #if type && table.type != type
77
+ # $stderr.puts "Table '#{db_name}.#{table_name} is not a #{type} table but a #{table.type} table"
78
+ #end
122
79
  table
123
80
  end
81
+
124
82
  end
125
83
  end
126
-
@@ -2,14 +2,52 @@
2
2
  module TreasureData
3
3
  module Command
4
4
 
5
- def create_database
6
- op = cmd_opt 'create-database', :db_name
5
+ def db_show(op)
7
6
  db_name = op.cmd_parse
8
7
 
9
8
  client = get_client
10
9
 
10
+ db = get_database(client, db_name)
11
+
12
+ rows = []
13
+ db.tables.each {|table|
14
+ pschema = table.schema.fields.map {|f|
15
+ "#{f.name}:#{f.type}"
16
+ }.join(', ')
17
+ rows << {:Table => table.name, :Type => table.type.to_s, :Count => table.count.to_s, :Schema=>pschema.to_s}
18
+ }
19
+ rows = rows.sort_by {|map|
20
+ [map[:Type].size, map[:Table]]
21
+ }
22
+
23
+ puts cmd_render_table(rows, :fields => [:Table, :Type, :Count, :Schema])
24
+ end
25
+
26
+ def db_list(op)
27
+ op.cmd_parse
28
+
29
+ client = get_client
30
+ dbs = client.databases
31
+
32
+ rows = []
33
+ dbs.each {|db|
34
+ rows << {:Name => db.name}
35
+ }
36
+ puts cmd_render_table(rows, :fields => [:Name])
37
+
38
+ if dbs.empty?
39
+ $stderr.puts "There are no databases."
40
+ $stderr.puts "Use '#{$prog} create-database <db_name>' to create a database."
41
+ end
42
+ end
43
+
44
+ def db_create(op)
45
+ db_name = op.cmd_parse
46
+
11
47
  API.validate_database_name(db_name)
12
48
 
49
+ client = get_client
50
+
13
51
  begin
14
52
  client.create_database(db_name)
15
53
  rescue AlreadyExistsError
@@ -21,11 +59,7 @@ module Command
21
59
  $stderr.puts "Use '#{$prog} create-log-table #{db_name} <table_name>' to create a table."
22
60
  end
23
61
 
24
- def drop_database
25
- op = cmd_opt 'drop-database', :db_name
26
-
27
- op.banner << "\noptions:\n"
28
-
62
+ def db_delete(op)
29
63
  force = false
30
64
  op.on('-f', '--force', 'clear tables and delete the database', TrueClass) {|b|
31
65
  force = true
@@ -46,35 +80,12 @@ module Command
46
80
  db.delete
47
81
  rescue NotFoundError
48
82
  $stderr.puts "Database '#{db_name}' does not exist."
49
- eixt 1
83
+ exit 1
50
84
  end
51
85
 
52
86
  $stderr.puts "Database '#{db_name}' is deleted."
53
87
  end
54
88
 
55
- def show_databases
56
- op = cmd_opt 'show-databases'
57
- op.cmd_parse
58
-
59
- client = get_client
60
-
61
- dbs = client.databases
62
-
63
- rows = []
64
- dbs.each {|db|
65
- rows << {:Name => db.name}
66
- }
67
- puts cmd_render_table(rows, :fields => [:Name])
68
-
69
- if dbs.empty?
70
- $stderr.puts "There are no databases."
71
- $stderr.puts "Use '#{$prog} create-database <db_name>' to create a database."
72
- end
73
- end
74
-
75
- alias show_dbs show_databases
76
- alias create_db create_database
77
- alias drop_db drop_database
78
89
  end
79
90
  end
80
91
 
@@ -0,0 +1,19 @@
1
+
2
+ module TreasureData
3
+ module Command
4
+
5
+ def help(op)
6
+ cmd = op.cmd_parse
7
+
8
+ op = List.get_option(cmd)
9
+ unless op
10
+ $stderr.puts "'#{cmd}' is not a td command. Run '#{$prog}' to show the list."
11
+ List.show_guess(cmd)
12
+ exit 1
13
+ end
14
+
15
+ puts op.message
16
+ end
17
+
18
+ end
19
+ end
@@ -16,17 +16,13 @@ module Command
16
16
  # TODO import-item
17
17
  # TODO tail
18
18
 
19
- def import
20
- op = cmd_opt 'import', :db_name, :table_name, :files_
21
-
19
+ def table_import(op)
22
20
  op.banner << "\nsupported formats:\n"
23
21
  op.banner << " apache\n"
24
22
  op.banner << " syslog\n"
25
23
  op.banner << " msgpack\n"
26
24
  op.banner << " json\n"
27
25
 
28
- op.banner << "\noptions:\n"
29
-
30
26
  format = 'apache'
31
27
  time_key = nil
32
28
 
@@ -54,7 +50,8 @@ module Command
54
50
  time_key = s
55
51
  }
56
52
 
57
- db_name, table_name, *paths = op.cmd_parse
53
+ table_ident, *paths = op.cmd_parse
54
+ db_name, table_name = parse_table_ident(table_ident)
58
55
 
59
56
  client = get_client
60
57
 
@@ -81,7 +78,7 @@ module Command
81
78
  parser = TextParser.new(names, regexp, time_format)
82
79
  end
83
80
 
84
- find_table(client, db_name, table_name, :log)
81
+ get_table(client, db_name, table_name)
85
82
 
86
83
  require 'zlib'
87
84
 
@@ -176,6 +173,7 @@ module Command
176
173
  file.each_line {|line|
177
174
  i += 1
178
175
  begin
176
+ line.rstrip!
179
177
  m = @regexp.match(line)
180
178
  unless m
181
179
  raise "invalid log format at #{path}:#{i}"
@@ -0,0 +1,235 @@
1
+
2
+ module TreasureData
3
+ module Command
4
+
5
+ def job_list(op)
6
+ page = 0
7
+ skip = 0
8
+
9
+ op.on('-p', '--page PAGE', 'skip N pages', Integer) {|i|
10
+ page = i
11
+ }
12
+ op.on('-s', '--skip N', 'skip N jobs', Integer) {|i|
13
+ skip = i
14
+ }
15
+
16
+ max = op.cmd_parse
17
+
18
+ max = (max || 20).to_i
19
+
20
+ client = get_client
21
+
22
+ if page
23
+ skip += max * page
24
+ end
25
+ jobs = client.jobs(skip, skip+max-1)
26
+
27
+ rows = []
28
+ jobs.each {|job|
29
+ start = job.start_at
30
+ finish = job.end_at
31
+ if start
32
+ if !finish
33
+ finish = Time.now.utc
34
+ end
35
+ e = finish.to_i - start.to_i
36
+ elapsed = ''
37
+ if e >= 3600
38
+ elapsed << "#{e/3600}h "
39
+ e %= 3600
40
+ elapsed << "% 2dm " % (e/60)
41
+ e %= 60
42
+ elapsed << "% 2dsec" % e
43
+ elsif e >= 60
44
+ elapsed << "% 2dm " % (e/60)
45
+ e %= 60
46
+ elapsed << "% 2dsec" % e
47
+ else
48
+ elapsed << "% 2dsec" % e
49
+ end
50
+ else
51
+ elapsed = ''
52
+ end
53
+ elapsed = "% 10s" % elapsed # right aligned
54
+
55
+ rows << {:JobID => job.job_id, :Status => job.status, :Query => job.query.to_s, :Start => start, :Elapsed => elapsed}
56
+ }
57
+
58
+ puts cmd_render_table(rows, :fields => [:JobID, :Status, :Start, :Elapsed, :Query])
59
+ end
60
+
61
+ def job_show(op)
62
+ verbose = nil
63
+ wait = false
64
+ output = nil
65
+ format = 'tsv'
66
+
67
+ op.on('-v', '--verbose', 'show logs', TrueClass) {|b|
68
+ verbose = b
69
+ }
70
+ op.on('-w', '--wait', 'wait for finishing the job', TrueClass) {|b|
71
+ wait = b
72
+ }
73
+ op.on('-o', '--output PATH', 'write result to the file') {|s|
74
+ output = s
75
+ }
76
+ op.on('-f', '--format FORMAT', 'format of the result to write to the file (tsv, csv, json or msgpack)') {|s|
77
+ unless ['tsv', 'csv', 'json', 'msgpack'].include?(s)
78
+ raise "Unknown format #{s.dump}. Supported format: tsv, csv, json, msgpack"
79
+ end
80
+ format = s
81
+ }
82
+
83
+ job_id = op.cmd_parse
84
+
85
+ client = get_client
86
+
87
+ job = client.job(job_id)
88
+
89
+ puts "JobID : #{job.job_id}"
90
+ puts "URL : #{job.url}"
91
+ puts "Status : #{job.status}"
92
+ puts "Query : #{job.query}"
93
+
94
+ if wait && !job.finished?
95
+ wait_job(job)
96
+ if job.success?
97
+ puts "Result :"
98
+ show_result(job, output, format)
99
+ end
100
+
101
+ else
102
+ if job.success?
103
+ puts "Result :"
104
+ show_result(job, output, format)
105
+ end
106
+
107
+ if verbose
108
+ puts ""
109
+ puts "cmdout:"
110
+ job.debug['cmdout'].to_s.split("\n").each {|line|
111
+ puts " "+line
112
+ }
113
+ puts ""
114
+ puts "stderr:"
115
+ job.debug['stderr'].to_s.split("\n").each {|line|
116
+ puts " "+line
117
+ }
118
+ end
119
+ end
120
+
121
+ $stderr.puts "Use '-v' option to show detailed messages." unless verbose
122
+ end
123
+
124
+ def job_kill(op)
125
+ job_id = op.cmd_parse
126
+
127
+ client = get_client
128
+
129
+ client.kill_job(job_id)
130
+ # TODO error
131
+ end
132
+
133
+ private
134
+ def wait_job(job)
135
+ $stderr.puts "running..."
136
+
137
+ cmdout_lines = 0
138
+ stderr_lines = 0
139
+
140
+ until job.finished?
141
+ sleep 2
142
+
143
+ job.update_status!
144
+
145
+ cmdout = job.debug['cmdout'].to_s.split("\n")[cmdout_lines..-1] || []
146
+ stderr = job.debug['stderr'].to_s.split("\n")[stderr_lines..-1] || []
147
+ (cmdout + stderr).each {|line|
148
+ puts " "+line
149
+ }
150
+ cmdout_lines += cmdout.size
151
+ stderr_lines += stderr.size
152
+ end
153
+ end
154
+
155
+ def show_result(job, output, format)
156
+ if output
157
+ write_result(job, output, format)
158
+ puts "written to #{output} in #{format} format"
159
+ else
160
+ render_result(job)
161
+ end
162
+ end
163
+
164
+ def write_result(job, output, format)
165
+ case format
166
+ when 'json'
167
+ require 'json'
168
+ first = true
169
+ File.open(output, "w") {|f|
170
+ f.write "["
171
+ job.result_each {|row|
172
+ if first
173
+ first = false
174
+ else
175
+ f.write ","
176
+ end
177
+ f.write row.to_json
178
+ }
179
+ f.write "]"
180
+ }
181
+
182
+ when 'msgpack'
183
+ File.open(output, "w") {|f|
184
+ f.write job.result_format('msgpack')
185
+ }
186
+
187
+ when 'csv'
188
+ require 'json'
189
+ require 'csv'
190
+ CSV.open(output, "w") {|writer|
191
+ job.result_each {|row|
192
+ writer << row.map {|col| col.is_a?(String) ? col.to_s : col.to_json }
193
+ }
194
+ }
195
+
196
+ when 'tsv'
197
+ require 'json'
198
+ File.open(output, "w") {|f|
199
+ job.result_each {|row|
200
+ first = true
201
+ row.each {|col|
202
+ if first
203
+ first = false
204
+ else
205
+ f.write "\t"
206
+ end
207
+ f.write col.is_a?(String) ? col.to_s : col.to_json
208
+ }
209
+ f.write "\n"
210
+ }
211
+ }
212
+
213
+ else
214
+ raise "Unknown format #{format.inspect}"
215
+ end
216
+ end
217
+
218
+ def render_result(job)
219
+ require 'json'
220
+ rows = []
221
+ job.result_each {|row|
222
+ # TODO limit number of rows to show
223
+ rows << row.map {|v|
224
+ if v.is_a?(String)
225
+ v.to_s
226
+ else
227
+ v.to_json
228
+ end
229
+ }
230
+ }
231
+ puts cmd_render_table(rows, :max_width=>10000)
232
+ end
233
+ end
234
+ end
235
+