mortar 0.15.26 → 0.15.27

Sign up to get free protection for your applications and to get access to all the features.
data/lib/mortar/auth.rb CHANGED
@@ -72,16 +72,24 @@ class Mortar::Auth
72
72
  @credentials = ask_for_and_save_credentials
73
73
  end
74
74
 
75
- def user # :nodoc:
76
- get_credentials[0]
75
+ def user(local=false) # :nodoc:
76
+ if (local && !has_credentials)
77
+ "notloggedin@user.org"
78
+ else
79
+ get_credentials[0]
80
+ end
77
81
  end
78
82
 
79
- def password # :nodoc:
80
- get_credentials[1]
83
+ def password(local=false) # :nodoc:
84
+ if (local && !has_credentials)
85
+ "notloggedin"
86
+ else
87
+ get_credentials[1]
88
+ end
81
89
  end
82
90
 
83
91
  def user_s3_safe(local = false)
84
- user_email = (local && !has_credentials) ? "notloggedin@user.org" : user
92
+ user_email = user(local)
85
93
  return user_email.gsub(/[^0-9a-zA-Z]/i, '-')
86
94
  end
87
95
 
@@ -104,6 +104,26 @@ class Mortar::Command::Base
104
104
  param_list
105
105
  end
106
106
 
107
+ def luigi_parameters
108
+ parameters = []
109
+ invalid_arguments.each_slice(2) do |arg_pair|
110
+ name_with_dashes = arg_pair[0]
111
+ unless (name_with_dashes.start_with? "--") && (name_with_dashes.length > 2)
112
+ error("Luigi parameter #{name_with_dashes} must begin with --")
113
+ end
114
+
115
+ unless arg_pair.length == 2
116
+ error("No value provided for luigi parameter #{name_with_dashes}")
117
+ end
118
+
119
+ name = name_with_dashes[2..name_with_dashes.length-1]
120
+ value = arg_pair[1]
121
+ parameters << {"name" => name, "value" => value}
122
+ end
123
+
124
+ parameters
125
+ end
126
+
107
127
  def pig_parameters
108
128
  paramfile_params = {}
109
129
  if options[:param_file]
@@ -23,7 +23,7 @@ require "mortar/command/base"
23
23
  #
24
24
  class Mortar::Command::Help < Mortar::Command::Base
25
25
 
26
- PRIMARY_NAMESPACES = %w( auth clusters generate illustrate jobs pigscripts projects )
26
+ PRIMARY_NAMESPACES = %w( auth config clusters generate jobs local luigi projects )
27
27
 
28
28
  # help [COMMAND]
29
29
  #
@@ -164,7 +164,7 @@ class Mortar::Command::Jobs < Mortar::Command::Base
164
164
  cluster_type = CLUSTER_TYPE__PERMANENT
165
165
  end
166
166
  use_spot_instances = options[:spot] || false
167
- api.post_job_new_cluster(project_name, script_name, git_ref, cluster_size,
167
+ api.post_pig_job_new_cluster(project_name, script_name, git_ref, cluster_size,
168
168
  :pig_version => pig_version.version,
169
169
  :project_script_path => script.rel_path,
170
170
  :parameters => pig_parameters,
@@ -174,7 +174,7 @@ class Mortar::Command::Jobs < Mortar::Command::Base
174
174
  :use_spot_instances => use_spot_instances).body
175
175
  else
176
176
  cluster_id = options[:clusterid]
177
- api.post_job_existing_cluster(project_name, script_name, git_ref, cluster_id,
177
+ api.post_pig_job_existing_cluster(project_name, script_name, git_ref, cluster_id,
178
178
  :pig_version => pig_version.version,
179
179
  :project_script_path => script.rel_path,
180
180
  :parameters => pig_parameters,
@@ -225,12 +225,12 @@ class Mortar::Command::Local < Mortar::Command::Base
225
225
 
226
226
  # local:luigi SCRIPT
227
227
  #
228
- # Run a luigi workflow on your local machine in local scheduler mode.
228
+ # Run a luigi pipeline script on your local machine in local scheduler mode.
229
229
  # Any additional command line arguments will be passed directly to the luigi script.
230
230
  #
231
- # -p, --parameter NAME=VALUE # Set a pig parameter value in your script.
232
- # -f, --param-file PARAMFILE # Load pig parameter values from a file.
233
231
  # --project-root PROJECTDIR # The root directory of the project if not the CWD
232
+ # -p, --parameter NAME=VALUE # [deprecated] Instead, pass luigi parameters directly as options (see below)
233
+ # -f, --param-file PARAMFILE # [deprecated] Instead, pass luigi parameters directly as options (see below)
234
234
  #
235
235
  #Examples:
236
236
  #
@@ -241,8 +241,7 @@ class Mortar::Command::Local < Mortar::Command::Base
241
241
  unless script_name
242
242
  error("Usage: mortar local:luigi SCRIPT\nMust specify SCRIPT.")
243
243
  end
244
- validate_arguments!
245
-
244
+
246
245
  # cd into the project root
247
246
  project_root = options[:project_root] ||= Dir.getwd
248
247
  unless File.directory?(project_root)
@@ -257,10 +256,24 @@ class Mortar::Command::Local < Mortar::Command::Base
257
256
  git_ref = sync_code_with_cloud()
258
257
  ENV['MORTAR_LUIGI_GIT_REF'] = git_ref
259
258
 
259
+ # pick up standard luigi-style params provided by the user
260
+ luigi_cli_parameters = luigi_parameters()
261
+
262
+ # pick up old pig-style parameters (included for backwards compatibility)
263
+ pig_style_parameters = pig_parameters()
264
+ if pig_style_parameters.length > 0
265
+ warn "[DEPRECATION] Passing luigi parameters with -p is deprecated. Please pass them directly (e.g. --myparam myvalue)"
266
+ end
267
+
268
+ luigi_cli_parameters.concat(pig_style_parameters)
269
+ cli_parameters = \
270
+ luigi_cli_parameters.sort_by { |p| p['name'] }.map { |arg| ["--#{arg['name']}", "#{arg['value']}"] }.flatten
271
+
272
+ # get project configuration parameters
273
+ project_config_params = config_parameters()
274
+
260
275
  ctrl = Mortar::Local::Controller.new
261
- luigi_params = pig_parameters.sort_by { |p| p['name'] }
262
- luigi_params = luigi_params.map { |arg| ["--#{arg['name']}", "#{arg['value']}"] }.flatten
263
- ctrl.run_luigi(pig_version, script, luigi_params)
276
+ ctrl.run_luigi(pig_version, script, cli_parameters, project_config_params)
264
277
  end
265
278
 
266
279
  # local:sqoop_table dbtype database-name table s3-destination
@@ -0,0 +1,86 @@
1
+ #
2
+ # Copyright 2014 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/command/base"
18
+ require "time"
19
+
20
+ # run luigi pipeline jobs
21
+ #
22
+ class Mortar::Command::Luigi < Mortar::Command::Base
23
+
24
+ include Mortar::Git
25
+
26
+ # luigi SCRIPT
27
+ #
28
+ # Run a luigi pipeline.
29
+ #
30
+ # -P, --project PROJECTNAME # Use a project that is not checked out in the current directory. Runs code from project's master branch in GitHub rather than snapshotting local code.
31
+ # -B, --branch BRANCHNAME # Used with --project to specify a non-master branch
32
+ #
33
+ # Examples:
34
+ #
35
+ # Run the nightly_rollup luigiscript:
36
+ # $ mortar luigi luigiscripts/nightly_rollup.py
37
+ #
38
+ # Run the nightly_rollup luigiscript with two parameters:
39
+ # $ mortar luigi luigiscripts/nightly_rollup.py --data-date 2012-02-01 --my-param myval
40
+ #
41
+ def index
42
+ script_name = shift_argument
43
+ unless script_name
44
+ error("Usage: mortar luigi SCRIPT\nMust specify SCRIPT.")
45
+ end
46
+
47
+ if options[:project]
48
+ project_name = options[:project]
49
+ if File.extname(script_name) == ".py"
50
+ script_name = File.basename(script_name, ".*")
51
+ end
52
+ else
53
+ project_name = project.name
54
+ script = validate_luigiscript!(script_name)
55
+ script_name = script.name
56
+ end
57
+
58
+ parameters = luigi_parameters()
59
+
60
+ if options[:project]
61
+ if options[:branch]
62
+ git_ref = options[:branch]
63
+ else
64
+ git_ref = "master"
65
+ end
66
+ else
67
+ git_ref = sync_code_with_cloud()
68
+ end
69
+
70
+ # post job to API
71
+ response = action("Requesting job execution") do
72
+ api.post_luigi_job(project_name, script_name, git_ref,
73
+ :project_script_path => script.rel_path,
74
+ :parameters => parameters).body
75
+ end
76
+
77
+ display("job_id: #{response['job_id']}")
78
+ display
79
+ display("Job status can be viewed on the web at:\n\n #{response['web_job_url']}")
80
+ display
81
+
82
+ response['job_id']
83
+ end
84
+
85
+ alias_command "luigi:run", "luigi"
86
+ end
@@ -72,6 +72,8 @@ module Mortar
72
72
 
73
73
  inside "luigiscripts" do
74
74
  copy_file "README", "README"
75
+ generate_file "luigiscript.py", "#{project_name}_luigi.py"
76
+ generate_file "client.cfg.template", "client.cfg.template"
75
77
  end
76
78
 
77
79
  mkdir "lib"
data/lib/mortar/git.rb CHANGED
@@ -160,6 +160,19 @@ module Mortar
160
160
  manifest_pathlist
161
161
  end
162
162
 
163
+ def add_entry_to_mortar_project_manifest(path, entry)
164
+ contents = File.open(path, "r") do |manifest|
165
+ manifest.read.strip
166
+ end
167
+
168
+ if contents && (! contents.include? entry)
169
+ new_contents = "#{contents}\n#{entry}\n"
170
+ File.open(path, "w") do |manifest|
171
+ manifest.write new_contents
172
+ end
173
+ end
174
+ end
175
+
163
176
  def add_newline_to_file(path)
164
177
  File.open(path, "r+") do |manifest|
165
178
  contents = manifest.read()
@@ -189,12 +202,25 @@ module Mortar
189
202
  #
190
203
  def ensure_valid_mortar_project_manifest()
191
204
  if File.exists? project_manifest_name
205
+ ensure_luigiscripts_in_project_manifest()
192
206
  add_newline_to_file(project_manifest_name)
193
207
  else
194
208
  create_mortar_project_manifest('.')
195
209
  end
196
210
  end
197
211
 
212
+ #
213
+ # Ensure that the luigiscripts directory,
214
+ # which was added after some project manifests were
215
+ # created, is in the manifest (if luigiscripts exists).
216
+ #
217
+ def ensure_luigiscripts_in_project_manifest
218
+ luigiscripts_path = "luigiscripts"
219
+ if File.directory? luigiscripts_path
220
+ add_entry_to_mortar_project_manifest(project_manifest_name, luigiscripts_path)
221
+ end
222
+ end
223
+
198
224
  #
199
225
  # Create a project manifest file
200
226
  #
@@ -207,6 +233,10 @@ module Mortar
207
233
  if File.directory? "#{path}/lib"
208
234
  manifest.puts "lib"
209
235
  end
236
+
237
+ if File.directory? "#{path}/luigiscripts"
238
+ manifest.puts "luigiscripts"
239
+ end
210
240
  end
211
241
  end
212
242
 
@@ -230,10 +230,14 @@ README
230
230
  pig.launch_repl(pig_version, pig_parameters)
231
231
  end
232
232
 
233
- def run_luigi(pig_version, luigi_script, user_script_args)
233
+ def run_luigi(pig_version, luigi_script, luigi_script_parameters, project_config_parameters)
234
+ require_aws_keys
234
235
  install_and_configure(pig_version, 'luigi')
235
236
  py = Mortar::Local::Python.new()
236
- py.run_luigi_script(luigi_script, user_script_args)
237
+ unless py.run_stillson_luigi_client_cfg_expansion(luigi_script, project_config_parameters)
238
+ error("Unable to expand your configuration template [luigiscripts/client.cfg.template] to [luigiscripts/client.cfg]")
239
+ end
240
+ py.run_luigi_script(luigi_script, luigi_script_parameters)
237
241
  end
238
242
 
239
243
  def sqoop_export_table(pig_version, connstr, dbtable, s3dest, options)
@@ -198,7 +198,12 @@ module Mortar
198
198
 
199
199
  def url_date(url, command=nil)
200
200
  result = head_resource(url, command)
201
- http_date_to_epoch(result.get_header('Last-Modified'))
201
+ last_modified = result.get_header('Last-Modified')
202
+ if last_modified
203
+ http_date_to_epoch(last_modified)
204
+ else
205
+ nil
206
+ end
202
207
  end
203
208
 
204
209
  # Given a subdirectory where we have installed some software
@@ -211,6 +216,9 @@ module Mortar
211
216
  return true
212
217
  end
213
218
  remote_archive_date = url_date(url, command)
219
+ if not remote_archive_date
220
+ return false
221
+ end
214
222
  return existing_install_date < remote_archive_date
215
223
  end
216
224
 
@@ -0,0 +1,68 @@
1
+ #
2
+ # Copyright 2014 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 'set'
18
+ require 'mortar/auth'
19
+
20
+ module Mortar
21
+ module Local
22
+ module Params
23
+
24
+ # Job parameters that are supplied automatically from Mortar when
25
+ # running on the server side. We duplicate these here.
26
+ def automatic_parameters()
27
+ params = {}
28
+
29
+ params['MORTAR_EMAIL'] = Mortar::Auth.user(true)
30
+ params['MORTAR_API_KEY'] = Mortar::Auth.password(true)
31
+
32
+ if ENV['MORTAR_EMAIL_S3_ESCAPED']
33
+ params['MORTAR_EMAIL_S3_ESCAPED'] = ENV['MORTAR_EMAIL_S3_ESCAPED']
34
+ else
35
+ params['MORTAR_EMAIL_S3_ESCAPED'] = Mortar::Auth.user_s3_safe(true)
36
+ end
37
+
38
+ if ENV['MORTAR_PROJECT_ROOT']
39
+ params['MORTAR_PROJECT_ROOT'] = ENV['MORTAR_PROJECT_ROOT']
40
+ else
41
+ params['MORTAR_PROJECT_ROOT'] = project_root
42
+ ENV['MORTAR_PROJECT_ROOT'] = params['MORTAR_PROJECT_ROOT']
43
+ end
44
+
45
+ params['AWS_ACCESS_KEY'] = ENV['AWS_ACCESS_KEY']
46
+ params['AWS_ACCESS_KEY_ID'] = ENV['AWS_ACCESS_KEY']
47
+ params['aws_access_key_id'] = ENV['AWS_ACCESS_KEY']
48
+
49
+ params['AWS_SECRET_KEY'] = ENV['AWS_SECRET_KEY']
50
+ params['AWS_SECRET_ACCESS_KEY'] = ENV['AWS_SECRET_KEY']
51
+ params['aws_secret_access_key'] = ENV['AWS_SECRET_KEY']
52
+
53
+ param_list = params.map do |k,v|
54
+ {"name" => k, "value" => v}
55
+ end
56
+ end
57
+
58
+ # Merge two lists of parameters, removing dupes.
59
+ # Parameters in param_list_1 override those in param_list_2
60
+ def merge_parameters(param_list_0, param_list_1)
61
+ param_list_1_keys = Set.new(param_list_1.map{|item| item["name"]})
62
+ merged = param_list_1.clone
63
+ merged.concat(param_list_0.select{|item| (! param_list_1_keys.include? item["name"]) })
64
+ merged
65
+ end
66
+ end
67
+ end
68
+ end
@@ -18,9 +18,11 @@ require "erb"
18
18
  require 'tempfile'
19
19
  require "mortar/helpers"
20
20
  require "mortar/local/installutil"
21
+ require "mortar/local/params"
21
22
 
22
23
  class Mortar::Local::Pig
23
24
  include Mortar::Local::InstallUtil
25
+ include Mortar::Local::Params
24
26
 
25
27
  PIG_LOG_FORMAT = "humanreadable"
26
28
  LIB_TGZ_NAME = "lib-common.tar.gz"
@@ -399,39 +401,11 @@ class Mortar::Local::Pig
399
401
  return opts
400
402
  end
401
403
 
402
- # Pig Paramenters that are supplied directly from Mortar when
403
- # running on the server side. We duplicate these here.
404
- def automatic_pig_parameters
405
- params = {}
406
-
407
- if ENV['MORTAR_EMAIL_S3_ESCAPED']
408
- params['MORTAR_EMAIL_S3_ESCAPED'] = ENV['MORTAR_EMAIL_S3_ESCAPED']
409
- else
410
- params['MORTAR_EMAIL_S3_ESCAPED'] = Mortar::Auth.user_s3_safe(true)
411
- end
412
-
413
- if ENV['MORTAR_PROJECT_ROOT']
414
- params['MORTAR_PROJECT_ROOT'] = ENV['MORTAR_PROJECT_ROOT']
415
- else
416
- params['MORTAR_PROJECT_ROOT'] = project_root
417
- ENV['MORTAR_PROJECT_ROOT'] = params['MORTAR_PROJECT_ROOT']
418
- end
419
-
420
-
421
- # Coerce into the same format as pig parameters that were
422
- # passed in via the command line or a parameter file
423
- param_list = []
424
- params.each{ |k,v|
425
- param_list.push({"name" => k, "value" => v})
426
- }
427
- return param_list
428
- end
429
-
430
404
  # Given a set of user specified pig parameters, combine with the
431
405
  # automatic mortar parameters and write out to a tempfile, returning
432
406
  # it's path so it may be referenced later in the process
433
407
  def make_pig_param_file(pig_parameters)
434
- mortar_pig_params = automatic_pig_parameters
408
+ mortar_pig_params = automatic_parameters()
435
409
  all_parameters = mortar_pig_params.concat(pig_parameters)
436
410
  param_file = Tempfile.new("mortar-pig-parameters")
437
411
  all_parameters.each { |p|
@@ -447,4 +421,9 @@ class Mortar::Local::Pig
447
421
  param_file.path
448
422
  end
449
423
 
424
+ def automatic_pig_parameters
425
+ warn "[DEPRECATION] Please call automatic_parameters instead"
426
+ automatic_parameters
427
+ end
428
+
450
429
  end