mortar 0.6.2 → 0.7.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.
@@ -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