mortar 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,94 @@
1
+ #
2
+ # Copyright 2012 Mortar Data Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'zlib'
18
+ require 'excon'
19
+ require 'rbconfig'
20
+ require 'rubygems/package'
21
+
22
+ require 'mortar/helpers'
23
+
24
+ module Mortar
25
+ module Local
26
+ module InstallUtil
27
+
28
+ include Mortar::Helpers
29
+
30
+ def local_install_directory
31
+ # note: assumes that CWD is the project root, is
32
+ # this a safe assumption?
33
+ File.join(Dir.getwd, ".mortar-local")
34
+ end
35
+
36
+
37
+ # Drops a marker file for an installed package, used
38
+ # to help determine if updates should be performed
39
+ def note_install(subdirectory)
40
+ install_file = install_file_for(subdirectory)
41
+ File.open(install_file, "w") do |install_file|
42
+ # Write out the current epoch so we know when this
43
+ # dependency was installed
44
+ install_file.write("#{Time.now.to_i}\n")
45
+ end
46
+ end
47
+
48
+ def install_date(subsection)
49
+ install_file = install_file_for(subsection)
50
+ if File.exists?(install_file)
51
+ File.open(install_file, "r") do |f|
52
+ file_contents = f.read()
53
+ file_contents.strip.to_i
54
+ end
55
+ end
56
+ end
57
+
58
+ def install_file_for(subdirectory)
59
+ File.join(local_install_directory, subdirectory, "install-date.txt")
60
+ end
61
+
62
+ # Given a path to a foo.tgz or foo.tar.gz file, extracts its
63
+ # contents to the specified output directory
64
+ def extract_tgz(tgz_path, dest_dir)
65
+ FileUtils.mkdir_p(dest_dir)
66
+ Gem::Package::TarReader.new(Zlib::GzipReader.open(tgz_path)).each do |entry|
67
+ entry_path = File.join(dest_dir, entry.full_name)
68
+ if entry.directory?
69
+ FileUtils.mkdir_p(entry_path)
70
+ elsif entry.file?
71
+ File.open(entry_path, "wb") do |entry_file|
72
+ entry_file.write(entry.read)
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ # Downloads the file at a specified url into the supplied director
79
+ def download_file(url, dest_dir)
80
+ dest_file_path = dest_dir + "/" + File.basename(url)
81
+ File.open(dest_file_path, "wb") do |dest_file|
82
+ contents = Excon.get(url).body
83
+ dest_file.write(contents)
84
+ end
85
+ end
86
+
87
+ def osx?
88
+ os_platform_name = RbConfig::CONFIG['target_os']
89
+ return os_platform_name.start_with?('darwin')
90
+ end
91
+
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,48 @@
1
+ #
2
+ # Copyright 2012 Mortar Data Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require "mortar/local/installutil"
18
+
19
+ class Mortar::Local::Java
20
+ include Mortar::Local::InstallUtil
21
+
22
+ @command = nil
23
+
24
+ def check_install
25
+ jbin = File.join(ENV['JAVA_HOME']||'', "bin", "java")
26
+ if ENV['JAVA_HOME'] and File.exists?(jbin)
27
+ @command = jbin
28
+ return true
29
+ elsif File.exists?("/usr/libexec/java_home")
30
+ # OSX has a nice little tool for finding this value, assuming
31
+ # that it won't give us a bad value
32
+ java_home = run_java_home
33
+ if java_home != ""
34
+ ENV['JAVA_HOME'] = java_home
35
+ @command = File.join(ENV['JAVA_HOME'], "bin", "java")
36
+ return true
37
+ end
38
+ end
39
+ return false
40
+ end
41
+
42
+ # Runs the java_home command line tool which on osx will
43
+ # return the appropriate JAVA_home value
44
+ def run_java_home
45
+ `/usr/libexec/java_home`.to_s.strip
46
+ end
47
+
48
+ end
@@ -0,0 +1,309 @@
1
+ #
2
+ # Copyright 2012 Mortar Data Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require "erb"
18
+ require 'tempfile'
19
+ require "mortar/helpers"
20
+ require "mortar/local/installutil"
21
+
22
+ class Mortar::Local::Pig
23
+ include Mortar::Local::InstallUtil
24
+
25
+ PIG_LOG_FORMAT = "humanreadable"
26
+ PIG_TAR_DEFAULT_URL = "https://s3.amazonaws.com/mortar-public-artifacts/pig.tgz"
27
+
28
+ # Tempfile objects have a hook to delete the file when the object is
29
+ # destroyed by the garbage collector. In practice this means that a
30
+ # file we want sitting around could disappear out from under us. To
31
+ # prevent this behavior, we're keeping references to these objects so
32
+ # that the garbage collector will not destroy them until the program
33
+ # exits (and our files won't be deleted until we don't care about them
34
+ # any more).
35
+ @temp_file_objects
36
+
37
+ # We copy some resources to the user's illustrate-output directory
38
+ # for styling the output. This only happens if they are not already present.
39
+ @resource_locations
40
+ @resource_destinations
41
+
42
+ attr_accessor :resource_locations
43
+ attr_accessor :resource_destinations
44
+
45
+ def initialize
46
+ @temp_file_objects = []
47
+
48
+ @resource_locations = {
49
+ "illustrate_template" => File.expand_path("../../templates/report/illustrate-report.html", __FILE__),
50
+ "illustrate_css" => File.expand_path("../../../../css/illustrate.css", __FILE__),
51
+ "jquery" => File.expand_path("../../../../js/jquery-1.7.1.min.js", __FILE__),
52
+ "jquery_transit" => File.expand_path("../../../../js/jquery.transit.js", __FILE__),
53
+ "jquery_stylestack" => File.expand_path("../../../../js/jquery.stylestack.js", __FILE__),
54
+ "mortar_table" => File.expand_path("../../../../js/mortar-table.js", __FILE__),
55
+ "zeroclipboard" => File.expand_path("../../../../js/zero_clipboard.js", __FILE__),
56
+ "zeroclipboard_swf" => File.expand_path("../../../../flash/zeroclipboard.swf", __FILE__)
57
+ }
58
+
59
+ @resource_destinations = {
60
+ "illustrate_html" => "illustrate-output/illustrate-output.html",
61
+ "illustrate_css" => "illustrate-output/resources/css/illustrate-output.css",
62
+ "jquery" => "illustrate-output/resources/js/jquery-1.7.1.min.js",
63
+ "jquery_transit" => "illustrate-output/resources/js/jquery.transit.js",
64
+ "jquery_stylestack" => "illustrate-output/resources/js/jquery.stylestack.js",
65
+ "mortar_table" => "illustrate-output/resources/js/mortar-table.js",
66
+ "zeroclipboard" => "illustrate-output/resources/js/zero_clipboard.js",
67
+ "zeroclipboard_swf" => "illustrate-output/resources/flash/zeroclipboard.swf"
68
+ }
69
+ end
70
+
71
+ def command
72
+ return File.join(pig_directory, "bin", "pig")
73
+ end
74
+
75
+ def pig_directory
76
+ return File.join(local_install_directory, "pig")
77
+ end
78
+
79
+ def pig_archive_url
80
+ ENV.fetch('PIG_DISTRO_URL', PIG_TAR_DEFAULT_URL)
81
+ end
82
+
83
+ def pig_archive_file
84
+ File.basename(pig_archive_url)
85
+ end
86
+
87
+ # Determines if a pig install needs to occur, true if no
88
+ # pig install present or a newer version is available
89
+ def should_do_pig_install?
90
+ not (File.exists?(pig_directory))
91
+ end
92
+
93
+ # Installs pig for this project if it is not already present
94
+ def install
95
+ if should_do_pig_install?
96
+ FileUtils.mkdir_p(local_install_directory)
97
+ action "Installing pig" do
98
+ download_file(pig_archive_url, local_install_directory)
99
+ local_tgz = File.join(local_install_directory, pig_archive_file)
100
+ extract_tgz(local_tgz, local_install_directory)
101
+
102
+ # This has been seening coming out of the tgz w/o +x so we do
103
+ # here to be sure it has the necessary permissions
104
+ FileUtils.chmod(0755, command)
105
+
106
+ File.delete(local_tgz)
107
+ note_install("pig")
108
+ end
109
+ end
110
+ end
111
+
112
+ # run the pig script with user supplied pig parameters
113
+ def run_script(pig_script, pig_parameters)
114
+ run_pig_command(" -f #{pig_script.path}", pig_parameters)
115
+ end
116
+
117
+ # Create a temp file to be used for writing the illustrate
118
+ # json output, and return it's path. This data file will
119
+ # later be used to create the result html output. Tempfile
120
+ # will take care of cleaning up the file when we exit.
121
+ def create_illustrate_output_path
122
+ # Using Tempfile for the path generation and so that the
123
+ # file will be cleaned up on process exit
124
+ outfile = Tempfile.new("mortar-illustrate-output")
125
+ outfile.close(false)
126
+ outfile.path
127
+ end
128
+
129
+ # Given a file path, open it and decode the containing json
130
+ def decode_illustrate_input_file(illustrate_outpath)
131
+ data_raw = File.read(illustrate_outpath)
132
+ begin
133
+ data_encoded = data_raw.encode('UTF-8', 'binary', :invalid => :replace, :undef => :replace, :replace => '')
134
+ rescue NoMethodError
135
+ require 'iconv'
136
+ ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
137
+ data_encoded = ic.iconv(data_raw)
138
+ end
139
+ json_decode(data_encoded)
140
+ end
141
+
142
+ def show_illustrate_output(illustrate_outpath)
143
+ ensure_dir_exists("illustrate-output")
144
+ ensure_dir_exists("illustrate-output/resources")
145
+ ensure_dir_exists("illustrate-output/resources/css")
146
+ ensure_dir_exists("illustrate-output/resources/js")
147
+ ensure_dir_exists("illustrate-output/resources/flash")
148
+
149
+ ["illustrate_css",
150
+ "jquery", "jquery_transit", "jquery_stylestack",
151
+ "mortar_table", "zeroclipboard", "zeroclipboard_swf"].each { |resource|
152
+ copy_if_not_present_at_dest(@resource_locations[resource], @resource_destinations[resource])
153
+ }
154
+
155
+ # Pull in the dumped json file
156
+ illustrate_data = decode_illustrate_input_file(illustrate_outpath)
157
+
158
+ # Render a template using it's values
159
+ template_params = create_illustrate_template_parameters(illustrate_data)
160
+
161
+ # template_params = {'tables' => []}
162
+ erb = ERB.new(File.read(@resource_locations["illustrate_template"]), 0, "%<>")
163
+ html = erb.result(BindingClazz.new(template_params).get_binding)
164
+
165
+ # Write the rendered template out to a file
166
+ File.open(@resource_destinations["illustrate_html"], 'w') { |f|
167
+ f.write(html)
168
+ }
169
+
170
+ # Open a browser pointing to the rendered template output file
171
+ action("Opening illustrate results from #{@resource_destinations["illustrate_html"]} ") do
172
+ require "launchy"
173
+ Launchy.open(File.expand_path(@resource_destinations["illustrate_html"]))
174
+ end
175
+
176
+ end
177
+
178
+ def create_illustrate_template_parameters(illustrate_data)
179
+ params = {}
180
+ params['tables'] = illustrate_data['tables']
181
+ params['udf_output'] = illustrate_data['udf_output']
182
+ return params
183
+ end
184
+
185
+ def illustrate_alias(pig_script, pig_alias, skip_pruning, pig_parameters)
186
+ cmd = "-e 'illustrate "
187
+
188
+ # Parameters have to be entered with the illustrate command (as
189
+ # apposed to as a command line argument) or it will result in an
190
+ # 'Undefined parameter' error.
191
+ param_file = make_pig_param_file(pig_parameters)
192
+ cmd += "-param_file #{param_file} "
193
+
194
+ # Now point us at the script/alias to illustrate
195
+ illustrate_outpath = create_illustrate_output_path()
196
+ cmd += "-script #{pig_script.path} -out #{illustrate_outpath} "
197
+ if skip_pruning
198
+ cmd += " -skipPruning "
199
+ end
200
+ cmd += " #{pig_alias} '"
201
+
202
+ run_pig_command(cmd, [])
203
+ show_illustrate_output(illustrate_outpath)
204
+ end
205
+
206
+ # Run pig with the specified command ('command' is anything that
207
+ # can be appended to the command line invocation of Pig that will
208
+ # get it to do something interesting, such as '-f some-file.pig'
209
+ def run_pig_command(cmd, parameters = nil)
210
+ # Generate the script for running the command, then
211
+ # write it to a temp script which will be exectued
212
+ script_text = script_for_command(cmd, parameters)
213
+ script = Tempfile.new("mortar-")
214
+ script.write(script_text)
215
+ script.close(false)
216
+ FileUtils.chmod(0755, script.path)
217
+ system(script.path)
218
+ script.unlink
219
+ end
220
+
221
+ # Generates a bash script which sets up the necessary environment and
222
+ # then runs the pig command
223
+ def script_for_command(cmd, parameters)
224
+ template_params = pig_command_script_template_parameters(cmd, parameters)
225
+ erb = ERB.new(File.read(pig_command_script_template_path), 0, "%<>")
226
+ erb.result(BindingClazz.new(template_params).get_binding)
227
+ end
228
+
229
+ # Path to the template which generates the bash script for running pig
230
+ def pig_command_script_template_path
231
+ File.expand_path("../../templates/script/runpig.sh", __FILE__)
232
+ end
233
+
234
+ # Parameters necessary for rendering the bash script template
235
+ def pig_command_script_template_parameters(cmd, pig_parameters)
236
+ template_params = {}
237
+ template_params['pig_params_file'] = make_pig_param_file(pig_parameters)
238
+ template_params['pig_home'] = pig_directory
239
+ template_params['pig_classpath'] = "#{pig_directory}/lib-pig/*"
240
+ template_params['classpath'] = "#{pig_directory}/lib/*:#{pig_directory}/conf/jets3t.properties"
241
+ template_params['project_home'] = File.expand_path("..", local_install_directory)
242
+ template_params['local_install_dir'] = local_install_directory
243
+ template_params['pig_sub_command'] = cmd
244
+ template_params['pig_opts'] = pig_options
245
+ template_params
246
+ end
247
+
248
+ # Returns a hash of settings that need to be passed
249
+ # in via pig options
250
+ def pig_options
251
+ opts = {}
252
+ opts['fs.s3n.awsAccessKeyId'] = ENV['AWS_ACCESS_KEY']
253
+ opts['fs.s3n.awsSecretAccessKey'] = ENV['AWS_SECRET_KEY']
254
+ opts['pig.events.logformat'] = PIG_LOG_FORMAT
255
+ return opts
256
+ end
257
+
258
+ # Pig Paramenters that are supplied directly from Mortar when
259
+ # running on the server side. We duplicate these here.
260
+ def automatic_pig_parameters
261
+ params = {}
262
+ if ENV['MORTAR_EMAIL_S3_ESCAPED']
263
+ params['MORTAR_EMAIL_S3_ESCAPED'] = ENV['MORTAR_EMAIL_S3_ESCAPED']
264
+ else
265
+ params['MORTAR_EMAIL_S3_ESCAPED'] = Mortar::Auth.user_s3_safe
266
+ end
267
+ # Coerce into the same format as pig parameters that were
268
+ # passed in via the command line or a parameter file
269
+ param_list = []
270
+ params.each{ |k,v|
271
+ param_list.push({"name" => k, "value" => v})
272
+ }
273
+ return param_list
274
+ end
275
+
276
+ # Given a set of user specified pig parameters, combine with the
277
+ # automatic mortar parameters and write out to a tempfile, returning
278
+ # it's path so it may be referenced later in the process
279
+ def make_pig_param_file(pig_parameters)
280
+ mortar_pig_params = automatic_pig_parameters
281
+ all_parameters = mortar_pig_params.concat(pig_parameters)
282
+ param_file = Tempfile.new("mortar-pig-parameters")
283
+ all_parameters.each { |p|
284
+ param_file.write("#{p['name']}=#{p['value']}\n")
285
+ }
286
+ param_file.close(false)
287
+
288
+ # Keep track a reference the tempfile object so that the
289
+ # garbage collector does not automatically delete the file
290
+ # out from under us
291
+ @temp_file_objects.push(param_file)
292
+
293
+ param_file.path
294
+ end
295
+
296
+ # Allows us to use a hash for template variables
297
+ class BindingClazz
298
+ def initialize(attrs)
299
+ attrs.each{ |k, v|
300
+ # set an instance variable with the key name so the binding will find it in scope
301
+ self.instance_variable_set("@#{k}".to_sym, v)
302
+ }
303
+ end
304
+ def get_binding()
305
+ binding
306
+ end
307
+ end
308
+
309
+ end