mortar 0.6.2 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|