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.
- data/lib/mortar/auth.rb +4 -0
- data/lib/mortar/command/base.rb +16 -0
- data/lib/mortar/command/describe.rb +7 -1
- data/lib/mortar/command/help.rb +1 -22
- data/lib/mortar/command/illustrate.rb +8 -3
- data/lib/mortar/command/jobs.rb +41 -14
- data/lib/mortar/command/local.rb +87 -0
- data/lib/mortar/command/projects.rb +12 -11
- data/lib/mortar/command/validate.rb +6 -1
- data/lib/mortar/helpers.rb +12 -0
- data/lib/mortar/local/controller.rb +108 -0
- data/lib/mortar/local/installutil.rb +94 -0
- data/lib/mortar/local/java.rb +48 -0
- data/lib/mortar/local/pig.rb +309 -0
- data/lib/mortar/local/python.rb +184 -0
- data/lib/mortar/project.rb +35 -9
- data/lib/mortar/templates/project/gitignore +3 -1
- data/lib/mortar/templates/report/illustrate-report.html +96 -0
- data/lib/mortar/templates/script/runpig.sh +23 -0
- data/lib/mortar/version.rb +1 -1
- data/spec/mortar/auth_spec.rb +8 -0
- data/spec/mortar/command/describe_spec.rb +14 -1
- data/spec/mortar/command/illustrate_spec.rb +14 -1
- data/spec/mortar/command/jobs_spec.rb +125 -7
- data/spec/mortar/command/local_spec.rb +144 -0
- data/spec/mortar/command/validate_spec.rb +14 -1
- data/spec/mortar/local/controller_spec.rb +102 -0
- data/spec/mortar/local/installutil_spec.rb +70 -0
- data/spec/mortar/local/java_spec.rb +62 -0
- data/spec/mortar/local/pig_spec.rb +157 -0
- data/spec/mortar/project_spec.rb +11 -0
- data/spec/spec_helper.rb +1 -0
- metadata +152 -130
@@ -0,0 +1,184 @@
|
|
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::Python
|
20
|
+
include Mortar::Local::InstallUtil
|
21
|
+
|
22
|
+
PYTHON_DEFAULT_TGZ_URL = "https://s3.amazonaws.com/mortar-public-artifacts/mortar-python-osx.tgz"
|
23
|
+
|
24
|
+
# Path to the python binary that should be used
|
25
|
+
# for running UDFs
|
26
|
+
@command = nil
|
27
|
+
|
28
|
+
|
29
|
+
# Execute either an installation of python or an inspection
|
30
|
+
# of the local system to see if a usable python is available
|
31
|
+
def check_or_install
|
32
|
+
if osx?
|
33
|
+
# We currently only install python for osx
|
34
|
+
install_python_osx
|
35
|
+
else
|
36
|
+
# Otherwise we check that the system supplied python will be sufficient
|
37
|
+
check_system_python
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Performs an installation of python specific to this project, this
|
42
|
+
# install includes pip and virtualenv
|
43
|
+
def install_python_osx
|
44
|
+
@command = "#{local_install_directory}/python/bin/python"
|
45
|
+
if should_do_python_install?
|
46
|
+
FileUtils.mkdir_p(local_install_directory)
|
47
|
+
action "Installing python" do
|
48
|
+
download_file(python_archive_url, local_install_directory)
|
49
|
+
extract_tgz(local_install_directory + "/" + python_archive_file, local_install_directory)
|
50
|
+
|
51
|
+
# This has been seening coming out of the tgz w/o +x so we do
|
52
|
+
# here to be sure it has the necessary permissions
|
53
|
+
FileUtils.chmod(0755, @command)
|
54
|
+
File.delete(local_install_directory + "/" + python_archive_file)
|
55
|
+
note_install("python")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
true
|
59
|
+
end
|
60
|
+
|
61
|
+
# Determines if a python install needs to occur, true if no
|
62
|
+
# python install present or a newer version is available
|
63
|
+
def should_do_python_install?
|
64
|
+
return (osx? and (not (File.exists?(python_directory))))
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
# Checks if there is a usable versionpython already installed
|
69
|
+
def check_system_python
|
70
|
+
py_cmd = path_to_local_python()
|
71
|
+
if not py_cmd
|
72
|
+
false
|
73
|
+
else
|
74
|
+
@command = py_cmd
|
75
|
+
true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Checks if the specified python command has
|
80
|
+
# virtualenv installed
|
81
|
+
def check_virtualenv_installed(python)
|
82
|
+
`#{python} -m virtualenv --help`
|
83
|
+
if (0 != $?.to_i)
|
84
|
+
false
|
85
|
+
else
|
86
|
+
true
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def path_to_local_python
|
91
|
+
# Check several python commands in decending level of desirability
|
92
|
+
[ "python#{desired_python_minor_version}", "python" ].each{ |cmd|
|
93
|
+
path_to_python = `which #{cmd}`.to_s.strip
|
94
|
+
if path_to_python != ''
|
95
|
+
# todo: check for a minimum version (in the case of 'python')
|
96
|
+
if check_virtualenv_installed(path_to_python)
|
97
|
+
return path_to_python
|
98
|
+
end
|
99
|
+
end
|
100
|
+
}
|
101
|
+
return nil
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
def desired_python_minor_version
|
106
|
+
return "2.7"
|
107
|
+
end
|
108
|
+
|
109
|
+
def pip_requirements_path
|
110
|
+
return ENV.fetch('PIP_REQ_FILE', File.join(Dir.getwd, "udfs", "python", "requirements.txt"))
|
111
|
+
end
|
112
|
+
|
113
|
+
def has_python_requirements
|
114
|
+
return File.exists?(pip_requirements_path)
|
115
|
+
end
|
116
|
+
|
117
|
+
def python_env_dir
|
118
|
+
return "#{local_install_directory}/pythonenv"
|
119
|
+
end
|
120
|
+
|
121
|
+
def python_directory
|
122
|
+
return "#{local_install_directory}/python"
|
123
|
+
end
|
124
|
+
|
125
|
+
def python_archive_url
|
126
|
+
return ENV.fetch('PYTHON_DISTRO_URL', PYTHON_DEFAULT_TGZ_URL)
|
127
|
+
end
|
128
|
+
|
129
|
+
def python_archive_file
|
130
|
+
File.basename(python_archive_url)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Creates a virtualenv in a well known location and installs any packages
|
134
|
+
# necessary for the users python udf
|
135
|
+
def setup_project_python_environment
|
136
|
+
`#{@command} -m virtualenv #{python_env_dir}`
|
137
|
+
if 0 != $?.to_i
|
138
|
+
return false
|
139
|
+
end
|
140
|
+
if should_do_requirements_install
|
141
|
+
action "Installing python UDF dependencies" do
|
142
|
+
pip_output = `. #{python_env_dir}/bin/activate &&
|
143
|
+
#{python_env_dir}/bin/pip install --requirement #{pip_requirements_path}`
|
144
|
+
if 0 != $?.to_i
|
145
|
+
File.open(pip_error_log_path, 'w') { |f|
|
146
|
+
f.write(pip_output)
|
147
|
+
}
|
148
|
+
return false
|
149
|
+
end
|
150
|
+
note_install("pythonenv")
|
151
|
+
end
|
152
|
+
end
|
153
|
+
return true
|
154
|
+
end
|
155
|
+
|
156
|
+
def pip_error_log_path
|
157
|
+
return ENV.fetch('PIP_ERROR_LOG', "dependency_install.log")
|
158
|
+
end
|
159
|
+
|
160
|
+
# Whether or not we need to do a `pip install -r requirements.txt` because
|
161
|
+
# we've never done one before or the dependencies have changed
|
162
|
+
def should_do_requirements_install
|
163
|
+
if has_python_requirements
|
164
|
+
if not install_date('pythonenv')
|
165
|
+
# We've never done an install from requirements.txt before
|
166
|
+
return true
|
167
|
+
else
|
168
|
+
return (requirements_edit_date > install_date('pythonenv'))
|
169
|
+
end
|
170
|
+
else
|
171
|
+
return false
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Date of last change to the requirements file
|
176
|
+
def requirements_edit_date
|
177
|
+
if has_python_requirements
|
178
|
+
return File.mtime(pip_requirements_path).to_i
|
179
|
+
else
|
180
|
+
return nil
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
data/lib/mortar/project.rb
CHANGED
@@ -57,6 +57,19 @@ module Mortar
|
|
57
57
|
".pig")
|
58
58
|
@pigscripts
|
59
59
|
end
|
60
|
+
|
61
|
+
def controlscripts_path
|
62
|
+
File.join(@root_path, "controlscripts")
|
63
|
+
end
|
64
|
+
|
65
|
+
def controlscripts
|
66
|
+
@controlscripts ||= ControlScripts.new(
|
67
|
+
controlscripts_path,
|
68
|
+
"controlscripts",
|
69
|
+
".py",
|
70
|
+
:optional => true)
|
71
|
+
@controlscripts
|
72
|
+
end
|
60
73
|
|
61
74
|
def tmp_path
|
62
75
|
path = File.join(@root_path, "tmp")
|
@@ -75,10 +88,11 @@ module Mortar
|
|
75
88
|
|
76
89
|
include Enumerable
|
77
90
|
|
78
|
-
def initialize(path, name, filename_extension)
|
91
|
+
def initialize(path, name, filename_extension, optional=false)
|
79
92
|
@path = path
|
80
93
|
@name = name
|
81
94
|
@filename_extension = filename_extension
|
95
|
+
@optional = optional
|
82
96
|
@elements = elements
|
83
97
|
end
|
84
98
|
|
@@ -107,14 +121,15 @@ module Mortar
|
|
107
121
|
end
|
108
122
|
|
109
123
|
def elements
|
110
|
-
|
111
|
-
|
124
|
+
if File.directory? @path
|
125
|
+
# get {script_name => full_path}
|
126
|
+
file_paths = Dir[File.join(@path, "**", "*#{@filename_extension}")]
|
127
|
+
file_paths_hsh = file_paths.collect{|element_path| [element_name(element_path), element(element_name(element_path), element_path)]}.flatten
|
128
|
+
return Hash[*file_paths_hsh]
|
129
|
+
else
|
130
|
+
raise ProjectError, "Unable to find #{@name} directory in project" if not @optional
|
112
131
|
end
|
113
|
-
|
114
|
-
# get {script_name => full_path}
|
115
|
-
file_paths = Dir[File.join(@path, "**", "*#{@filename_extension}")]
|
116
|
-
file_paths_hsh = file_paths.collect{|element_path| [element_name(element_path), element(element_name(element_path), element_path)]}.flatten
|
117
|
-
Hash[*file_paths_hsh]
|
132
|
+
return Hash[]
|
118
133
|
end
|
119
134
|
|
120
135
|
def element(path)
|
@@ -124,7 +139,13 @@ module Mortar
|
|
124
139
|
|
125
140
|
class PigScripts < ProjectEntity
|
126
141
|
def element(name, path)
|
127
|
-
|
142
|
+
PigScript.new(name, path)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class ControlScripts < ProjectEntity
|
147
|
+
def element(name, path)
|
148
|
+
ControlScript.new(name, path)
|
128
149
|
end
|
129
150
|
end
|
130
151
|
|
@@ -156,5 +177,10 @@ module Mortar
|
|
156
177
|
end
|
157
178
|
end
|
158
179
|
|
180
|
+
class ControlScript < Script
|
181
|
+
end
|
182
|
+
class PigScript < Script
|
183
|
+
end
|
184
|
+
|
159
185
|
end
|
160
186
|
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Illustrate Report</title>
|
5
|
+
<link rel="stylesheet" href="resources/css/illustrate-output.css" type="text/css" />
|
6
|
+
</head>
|
7
|
+
|
8
|
+
<body>
|
9
|
+
|
10
|
+
<% @tables.each_with_index do |table, table_index| %>
|
11
|
+
|
12
|
+
<h3 class="illustrate_alias">
|
13
|
+
<% if (table["op"] == "LOStore") %>
|
14
|
+
Store :
|
15
|
+
<% end %>
|
16
|
+
<%= table["alias"] %>
|
17
|
+
<% if (table["notices"]) %>
|
18
|
+
|
19
|
+
<% end %>
|
20
|
+
</h3>
|
21
|
+
|
22
|
+
<table class="table table-bordered illustrate_table expandable">
|
23
|
+
<tbody>
|
24
|
+
<tr>
|
25
|
+
<% table["fields"].each do |field| %>
|
26
|
+
<th><%= field %></th>
|
27
|
+
<% end %>
|
28
|
+
</tr>
|
29
|
+
|
30
|
+
<% table["data"].each_with_index do |row, row_index| %>
|
31
|
+
<tr>
|
32
|
+
<% row.each_with_index do |value, item_index| %>
|
33
|
+
<% if (value) %>
|
34
|
+
<td>
|
35
|
+
<div class="mortar-table-expandable-cell-wrapper">
|
36
|
+
<div class="mortar-table-expandable-cell-container">
|
37
|
+
<div class="mortar-table-expandable-cell-preview">
|
38
|
+
<%# <%- $.mortar_data.truncate.truncate_center(value, 30) %>
|
39
|
+
<%= value[0..30] %>
|
40
|
+
</div>
|
41
|
+
<div class="mortar-table-expandable-cell-content" hidden="true">
|
42
|
+
<!-- TODO: get clipboard working -->
|
43
|
+
<!-- <div data-clipboard-target="copy-<%- table_index %>-<%- row_index %>-<%- item_index %>" class="clipboard" data->Copy to Clipboard</div> -->
|
44
|
+
<div id="copy-<%- table_index %>-<%- row_index %>-<%- item_index %>" class="illustrate_content_wrapper"><%= value %></div>
|
45
|
+
</div>
|
46
|
+
</div>
|
47
|
+
</div>
|
48
|
+
</td>
|
49
|
+
<% else %>
|
50
|
+
<td><div> </div></td>
|
51
|
+
<% end %>
|
52
|
+
<% end %>
|
53
|
+
</tr>
|
54
|
+
<% end %>
|
55
|
+
</tbody>
|
56
|
+
</table>
|
57
|
+
|
58
|
+
<% if (table["notices"]) %>
|
59
|
+
<% table["notices"].each do |notice| %>
|
60
|
+
<div class="alert">
|
61
|
+
<a class="close" data-dismiss="alert">×</a>
|
62
|
+
* <%= notice %>
|
63
|
+
</div>
|
64
|
+
<% end %>
|
65
|
+
<% end %>
|
66
|
+
|
67
|
+
<% end %>
|
68
|
+
|
69
|
+
<% if (@udf_output and (not @udf_output.empty?)) %>
|
70
|
+
<h3 class="illustrate_alias">UDF Output: </h3>
|
71
|
+
</br>
|
72
|
+
<pre id="illustrate_udf_output">
|
73
|
+
<%= @udf_output %>
|
74
|
+
</pre>
|
75
|
+
<% end %>
|
76
|
+
|
77
|
+
<script src="resources/js/jquery-1.7.1.min.js"></script>
|
78
|
+
<script src="resources/js/jquery.transit.js"></script>
|
79
|
+
<script src="resources/js/jquery.stylestack.js"></script>
|
80
|
+
<script src="resources/js/zero_clipboard.js"></script>
|
81
|
+
<script src="resources/js/mortar-table.js"></script>
|
82
|
+
<script>
|
83
|
+
$('.illustrate_table td').mortarTableExpandableCell();
|
84
|
+
// TODO: get this working
|
85
|
+
/*
|
86
|
+
$('.illustrate_table td .clipboard').each(function() {
|
87
|
+
$(this).data('clippy', new ZeroClipboard( $(this), {
|
88
|
+
moviePath: "resources/flash/zeroclipboard.swf"
|
89
|
+
}));
|
90
|
+
});
|
91
|
+
*/
|
92
|
+
</script>
|
93
|
+
|
94
|
+
</body>
|
95
|
+
|
96
|
+
</html>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
set -e
|
4
|
+
|
5
|
+
export PIG_HOME=<%= @pig_home %>
|
6
|
+
export PIG_CLASSPATH=<%= @pig_classpath %>
|
7
|
+
export CLASSPATH=<%= @classpath %>
|
8
|
+
export PIG_MAIN_CLASS=com.mortardata.hawk.HawkMain
|
9
|
+
export PIG_OPTS="<% @pig_opts.each do |k,v| %>-D<%= k %>=<%= v %> <% end %>"
|
10
|
+
|
11
|
+
# UDF paths are relative to this direectory
|
12
|
+
cd <%= @project_home %>/pigscripts
|
13
|
+
|
14
|
+
# Setup python environment
|
15
|
+
source <%= @local_install_dir %>/pythonenv/bin/activate
|
16
|
+
|
17
|
+
# Run Pig
|
18
|
+
<%= @local_install_dir %>/pig/bin/pig -exectype local \
|
19
|
+
-log4jconf <%= @local_install_dir %>/pig/conf/log4j-cli-local-dev.properties \
|
20
|
+
-propertyFile <%= @local_install_dir %>/pig/conf/pig-hawk-global.properties \
|
21
|
+
-propertyFile <%= @local_install_dir %>/pig/conf/pig-cli-local-dev.properties \
|
22
|
+
-param_file <%= @pig_params_file %> \
|
23
|
+
<%= @pig_sub_command %>
|
data/lib/mortar/version.rb
CHANGED
data/spec/mortar/auth_spec.rb
CHANGED
@@ -152,5 +152,13 @@ module Mortar
|
|
152
152
|
|
153
153
|
lambda { @cli.check }.should raise_error(Mortar::CLI::Errors::InvalidGithubUsername)
|
154
154
|
end
|
155
|
+
|
156
|
+
it "encodes the user email as s3 safe" do
|
157
|
+
user_email = "myemail+dontspam@somedomain.com"
|
158
|
+
stub(@cli).user.returns(user_email)
|
159
|
+
@cli.user().should == user_email
|
160
|
+
@cli.user_s3_safe.should == 'myemail-dontspam-somedomain-com'
|
161
|
+
end
|
162
|
+
|
155
163
|
end
|
156
164
|
end
|
@@ -57,8 +57,21 @@ STDERR
|
|
57
57
|
with_git_initialized_project do |p|
|
58
58
|
stderr, stdout = execute("describe does_not_exist my_alias", p, @git)
|
59
59
|
stderr.should == <<-STDERR
|
60
|
-
! Unable to find pigscript does_not_exist
|
60
|
+
! Unable to find a pigscript or controlscript for does_not_exist
|
61
|
+
!
|
61
62
|
! No pigscripts found
|
63
|
+
!
|
64
|
+
! No controlscripts found
|
65
|
+
STDERR
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it "errors when requested with controlscript" do
|
70
|
+
with_git_initialized_project do |p|
|
71
|
+
write_file(File.join(p.controlscripts_path, "my_script.py"))
|
72
|
+
stderr, stdout = execute("describe my_script my_alias", p, @git)
|
73
|
+
stderr.should == <<-STDERR
|
74
|
+
! Currently Mortar does not support describing control scripts
|
62
75
|
STDERR
|
63
76
|
end
|
64
77
|
end
|
@@ -57,8 +57,21 @@ STDERR
|
|
57
57
|
with_git_initialized_project do |p|
|
58
58
|
stderr, stdout = execute("illustrate does_not_exist my_alias", p, @git)
|
59
59
|
stderr.should == <<-STDERR
|
60
|
-
! Unable to find pigscript does_not_exist
|
60
|
+
! Unable to find a pigscript or controlscript for does_not_exist
|
61
|
+
!
|
61
62
|
! No pigscripts found
|
63
|
+
!
|
64
|
+
! No controlscripts found
|
65
|
+
STDERR
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it "errors when requested with controlscript" do
|
70
|
+
with_git_initialized_project do |p|
|
71
|
+
write_file(File.join(p.controlscripts_path, "my_script.py"))
|
72
|
+
stderr, stdout = execute("illustrate my_script my_alias", p, @git)
|
73
|
+
stderr.should == <<-STDERR
|
74
|
+
! Currently Mortar does not support illustrating control scripts
|
62
75
|
STDERR
|
63
76
|
end
|
64
77
|
end
|