mortar 0.1.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.
Files changed (61) hide show
  1. data/README.md +36 -0
  2. data/bin/mortar +13 -0
  3. data/lib/mortar.rb +23 -0
  4. data/lib/mortar/auth.rb +312 -0
  5. data/lib/mortar/cli.rb +54 -0
  6. data/lib/mortar/command.rb +267 -0
  7. data/lib/mortar/command/auth.rb +96 -0
  8. data/lib/mortar/command/base.rb +319 -0
  9. data/lib/mortar/command/clusters.rb +41 -0
  10. data/lib/mortar/command/describe.rb +97 -0
  11. data/lib/mortar/command/generate.rb +121 -0
  12. data/lib/mortar/command/help.rb +166 -0
  13. data/lib/mortar/command/illustrate.rb +97 -0
  14. data/lib/mortar/command/jobs.rb +174 -0
  15. data/lib/mortar/command/pigscripts.rb +45 -0
  16. data/lib/mortar/command/projects.rb +128 -0
  17. data/lib/mortar/command/validate.rb +94 -0
  18. data/lib/mortar/command/version.rb +42 -0
  19. data/lib/mortar/errors.rb +24 -0
  20. data/lib/mortar/generators/generator_base.rb +107 -0
  21. data/lib/mortar/generators/macro_generator.rb +37 -0
  22. data/lib/mortar/generators/pigscript_generator.rb +40 -0
  23. data/lib/mortar/generators/project_generator.rb +67 -0
  24. data/lib/mortar/generators/udf_generator.rb +28 -0
  25. data/lib/mortar/git.rb +233 -0
  26. data/lib/mortar/helpers.rb +488 -0
  27. data/lib/mortar/project.rb +156 -0
  28. data/lib/mortar/snapshot.rb +39 -0
  29. data/lib/mortar/templates/macro/macro.pig +14 -0
  30. data/lib/mortar/templates/pigscript/pigscript.pig +38 -0
  31. data/lib/mortar/templates/pigscript/python_udf.py +13 -0
  32. data/lib/mortar/templates/project/Gemfile +3 -0
  33. data/lib/mortar/templates/project/README.md +8 -0
  34. data/lib/mortar/templates/project/gitignore +4 -0
  35. data/lib/mortar/templates/project/macros/gitkeep +0 -0
  36. data/lib/mortar/templates/project/pigscripts/pigscript.pig +35 -0
  37. data/lib/mortar/templates/project/udfs/python/python_udf.py +13 -0
  38. data/lib/mortar/templates/udf/python_udf.py +13 -0
  39. data/lib/mortar/version.rb +20 -0
  40. data/lib/vendor/mortar/okjson.rb +598 -0
  41. data/lib/vendor/mortar/uuid.rb +312 -0
  42. data/spec/mortar/auth_spec.rb +156 -0
  43. data/spec/mortar/command/auth_spec.rb +46 -0
  44. data/spec/mortar/command/base_spec.rb +82 -0
  45. data/spec/mortar/command/clusters_spec.rb +61 -0
  46. data/spec/mortar/command/describe_spec.rb +135 -0
  47. data/spec/mortar/command/generate_spec.rb +139 -0
  48. data/spec/mortar/command/illustrate_spec.rb +140 -0
  49. data/spec/mortar/command/jobs_spec.rb +364 -0
  50. data/spec/mortar/command/pigscripts_spec.rb +70 -0
  51. data/spec/mortar/command/projects_spec.rb +165 -0
  52. data/spec/mortar/command/validate_spec.rb +119 -0
  53. data/spec/mortar/command_spec.rb +122 -0
  54. data/spec/mortar/git_spec.rb +278 -0
  55. data/spec/mortar/helpers_spec.rb +82 -0
  56. data/spec/mortar/project_spec.rb +76 -0
  57. data/spec/mortar/snapshot_spec.rb +46 -0
  58. data/spec/spec.opts +1 -0
  59. data/spec/spec_helper.rb +278 -0
  60. data/spec/support/display_message_matcher.rb +68 -0
  61. metadata +259 -0
@@ -0,0 +1,174 @@
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/command/base"
18
+ require "mortar/snapshot"
19
+ require "time"
20
+ # manage pig scripts
21
+ #
22
+ class Mortar::Command::Jobs < Mortar::Command::Base
23
+
24
+ include Mortar::Snapshot
25
+
26
+ # jobs
27
+ #
28
+ # Show recent and running jobs.
29
+ #
30
+ # -l, --limit LIMITJOBS # Limit the number of jobs returned (defaults to 10)
31
+ # -s, --skip SKIPJOBS # Skip a certain amount of jobs (defaults to 0)
32
+ #
33
+ # Examples:
34
+ #
35
+ # $ mortar jobs
36
+ #
37
+ # TBD
38
+ #
39
+ def index
40
+ options[:limit] ||= '10'
41
+ options[:skip] ||= '0'
42
+ jobs = api.get_jobs(options[:skip], options[:limit]).body['jobs']
43
+ jobs.each do |job|
44
+ if job['start_timestamp']
45
+ job['start_timestamp'] = Time.parse(job['start_timestamp']).strftime('%A, %B %e, %Y, %l:%M %p')
46
+ end
47
+ end
48
+ headers = [ 'job_id', 'script' , 'status' , 'start_date' , 'elapsed_time' , 'cluster_size' , 'cluster_id']
49
+ columns = [ 'job_id', 'display_name', 'status_description', 'start_timestamp', 'duration', 'cluster_size', 'cluster_id']
50
+ display_table(jobs, columns, headers)
51
+ end
52
+
53
+ # jobs:run PIGSCRIPT
54
+ #
55
+ # Run a job on a Mortar Hadoop cluster.
56
+ #
57
+ # -c, --clusterid CLUSTERID # Run job on an existing cluster with ID of CLUSTERID (optional)
58
+ # -s, --clustersize NUMNODES # Run job on a new cluster, with NUMNODES nodes (optional; must be >= 2 if provided)
59
+ # -1, --singlejobcluster # Stop the cluster after job completes. (Default: false—-cluster can be used for other jobs, and will shut down after 1 hour of inactivity)
60
+ # -p, --parameter NAME=VALUE # Set a pig parameter value in your script.
61
+ # -f, --param-file PARAMFILE # Load pig parameter values from a file.
62
+ #
63
+ #Examples:
64
+ #
65
+ # $ mortar jobs:run
66
+ #
67
+ # TBD
68
+ #
69
+ def run
70
+ # arguemnts
71
+ pigscript_name = shift_argument
72
+ unless pigscript_name
73
+ error("Usage: mortar jobs:run PIGSCRIPT\nMust specify PIGSCRIPT.")
74
+ end
75
+ validate_arguments!
76
+
77
+ unless options[:clusterid] || options[:clustersize]
78
+ error("Please provide either the --clustersize option to run job on a new cluster, or --clusterid to run on an existing one.")
79
+ end
80
+
81
+ if options[:clusterid]
82
+ [:clustersize, :singlejobcluster].each do |opt|
83
+ unless options[opt].nil?
84
+ error("Option #{opt.to_s} cannot be set when running a job on an existing cluster (with --clusterid option)")
85
+ end
86
+ end
87
+ end
88
+
89
+
90
+
91
+ validate_git_based_project!
92
+ pigscript = validate_pigscript!(pigscript_name)
93
+ git_ref = create_and_push_snapshot_branch(git, project)
94
+
95
+ # post job to API
96
+ response = action("Requesting job execution") do
97
+ if options[:clustersize]
98
+ cluster_size = options[:clustersize].to_i
99
+ keepalive = ! options[:singlejobcluster]
100
+ api.post_job_new_cluster(project.name, pigscript.name, git_ref, cluster_size,
101
+ :parameters => pig_parameters,
102
+ :keepalive => keepalive).body
103
+ else
104
+ cluster_id = options[:clusterid]
105
+ api.post_job_existing_cluster(project.name, pigscript.name, git_ref, cluster_id,
106
+ :parameters => pig_parameters).body
107
+ end
108
+ end
109
+
110
+ display("job_id: #{response['job_id']}")
111
+ display
112
+ display("Job status can be viewed on the web at:\n\n #{response['web_job_url']}")
113
+ display
114
+ display("Or by running:\n\n mortar jobs:status #{response['job_id']}")
115
+ display
116
+ end
117
+
118
+ alias_command "run", "jobs:run"
119
+
120
+
121
+ # jobs:status JOB_ID
122
+ #
123
+ # Check the status of a job.
124
+ #
125
+ #Examples:
126
+ #
127
+ # $ mortar jobs:status 84f3c86f20034ed4bf5e359120a47f5a
128
+ #
129
+ # TBD
130
+ def status
131
+ job_id = shift_argument
132
+ unless job_id
133
+ error("Usage: mortar jobs:status JOB_ID\nMust specify JOB_ID.")
134
+ end
135
+
136
+ job_status = api.get_job(job_id).body
137
+
138
+ job_display_entries = {
139
+ "status" => job_status["status_description"],
140
+ "progress" => "#{job_status["progress"]}%",
141
+ "cluster_id" => job_status["cluster_id"],
142
+ "job submitted at" => job_status["start_timestamp"],
143
+ "job began running at" => job_status["running_timestamp"],
144
+ "job finished at" => job_status["stop_timestamp"],
145
+ "job running for" => job_status["duration"],
146
+ "job run with parameters" => job_status["parameters"],
147
+ "error" => job_status["error"]
148
+ }
149
+
150
+ unless job_status["error"].nil? || job_status["error"]["message"].nil?
151
+ error_context = get_error_message_context(job_status["error"]["message"])
152
+ unless error_context == ""
153
+ job_status["error"]["help"] = error_context
154
+ end
155
+ end
156
+
157
+ if job_status["num_hadoop_jobs"] && job_status["num_hadoop_jobs_succeeded"]
158
+ job_display_entries["hadoop jobs complete"] =
159
+ '%0.2f / %0.2f' % [job_status["num_hadoop_jobs_succeeded"], job_status["num_hadoop_jobs"]]
160
+ end
161
+
162
+ if job_status["outputs"] && job_status["outputs"].length > 0
163
+ job_display_entries["outputs"] = Hash[job_status["outputs"].select{|o| o["alias"]}.collect do |output|
164
+ output_hash = {}
165
+ output_hash["location"] = output["location"] if output["location"]
166
+ output_hash["records"] = output["records"] if output["records"]
167
+ [output['alias'], output_hash]
168
+ end]
169
+ end
170
+
171
+ styled_header("#{job_status["project_name"]}: #{job_status["pigscript_name"]} (job_id: #{job_status["job_id"]})")
172
+ styled_hash(job_display_entries)
173
+ end
174
+ end
@@ -0,0 +1,45 @@
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/command/base"
18
+
19
+ # manage pig scripts
20
+ #
21
+ class Mortar::Command::PigScripts < Mortar::Command::Base
22
+
23
+ # pigscripts
24
+ #
25
+ # display the available set of pigscripts
26
+ #
27
+ #Examples:
28
+ #
29
+ # $ mortar pigscripts
30
+ #
31
+ # hourly_top_searchers
32
+ # user_engagement
33
+ #
34
+ def index
35
+ # validation
36
+ validate_arguments!
37
+ if project.pigscripts.any?
38
+ styled_header("pigscripts")
39
+ styled_array(project.pigscripts.keys)
40
+ else
41
+ display("You have no pigscripts.")
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,128 @@
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/command/base"
18
+
19
+ ## manage projects
20
+ #
21
+ class Mortar::Command::Projects < Mortar::Command::Base
22
+
23
+ # projects
24
+ #
25
+ # Display the available set of projects
26
+ #
27
+ #Examples:
28
+ #
29
+ # $ mortar projects
30
+ #
31
+ #=== projects
32
+ #demo
33
+ #rollup
34
+ #
35
+ def index
36
+ validate_arguments!
37
+ projects = api.get_projects().body["projects"]
38
+ if projects.any?
39
+ styled_header("projects")
40
+ styled_array(projects.collect{ |x| x["name"] })
41
+ else
42
+ display("You have no projects.")
43
+ end
44
+ end
45
+
46
+ # projects:create PROJECT
47
+ #
48
+ # create a mortar project for the current directory with the name PROJECT
49
+ #
50
+ #Example:
51
+ #
52
+ # $ mortar projects:create my_new_project
53
+ #
54
+ def create
55
+ name = shift_argument
56
+ unless name
57
+ error("Usage: mortar projects:create PROJECT\nMust specify PROJECT.")
58
+ end
59
+ validate_arguments!
60
+
61
+ unless git.has_dot_git?
62
+ error("Can only create a mortar project for an existing git project. Please run:\n\ngit init\ngit add .\ngit commit -a -m \"first commit\"\n\nto initialize your project in git.")
63
+ end
64
+
65
+ unless git.remotes(git_organization).empty?
66
+ begin
67
+ error("Currently in project: #{project.name}. You can not create a new project inside of an existing mortar project.")
68
+ rescue Mortar::Command::CommandFailed => cf
69
+ error("Currently in an existing Mortar project. You can not create a new project inside of an existing mortar project.")
70
+ end
71
+ end
72
+
73
+ project_id = nil
74
+ action("Creating project", {:success => "started"}) do
75
+ project_id = api.post_project(name).body["project_id"]
76
+ end
77
+
78
+ last_project_result = nil
79
+ while last_project_result.nil? || (! Mortar::API::Projects::STATUSES_COMPLETE.include?(last_project_result["status"]))
80
+ sleep polling_interval
81
+ current_project_result = api.get_project(project_id).body
82
+ if last_project_result.nil? || (last_project_result["status"] != current_project_result["status"])
83
+ display(" ... #{current_project_result['status']}")
84
+ end
85
+
86
+ last_project_result = current_project_result
87
+ end
88
+
89
+ case last_project_result['status']
90
+ when Mortar::API::Projects::STATUS_FAILED
91
+ error("Project creation failed.\nError message: #{last_project_result['error_message']}")
92
+ when Mortar::API::Projects::STATUS_ACTIVE
93
+ git.remote_add("mortar", last_project_result['git_url'])
94
+ else
95
+ raise RuntimeError, "Unknown project status: #{last_project_result['status']} for project_id: #{project_id}"
96
+ end
97
+
98
+
99
+ end
100
+
101
+ # projects:clone PROJECT
102
+ #
103
+ # clone the mortar project PROJECT into the current directory.
104
+ #
105
+ #Example:
106
+ #
107
+ # $ mortar projects:clone my_new_project
108
+ #
109
+ def clone
110
+ name = shift_argument
111
+ unless name
112
+ error("Usage: mortar projects:clone PROJECT\nMust specify PROJECT.")
113
+ end
114
+ validate_arguments!
115
+ projects = api.get_projects().body["projects"]
116
+ project = projects.find{|p| p['name'] == name}
117
+ unless project
118
+ error("No project named: #{name} exists. Your valid projects are:\n#{projects.collect{ |x| x["name"]}.join("\n")}")
119
+ end
120
+
121
+ project_dir = File.join(Dir.pwd, project['name'])
122
+ unless !File.exists?(project_dir)
123
+ error("Can't clone project: #{project['name']} since directory with that name already exists.")
124
+ end
125
+
126
+ git.clone(project['git_url'], project['name'])
127
+ end
128
+ end
@@ -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 "mortar/command/base"
18
+ require "mortar/snapshot"
19
+
20
+ # manage pig scripts
21
+ #
22
+ class Mortar::Command::Validate < Mortar::Command::Base
23
+
24
+ include Mortar::Snapshot
25
+
26
+ # validate [PIGSCRIPT]
27
+ #
28
+ # Validate a pig script. Checks script for problems with:
29
+ # * Pig syntax
30
+ # * Python syntax
31
+ # * S3 data access
32
+ #
33
+ # -p, --parameter NAME=VALUE # Set a pig parameter value in your script.
34
+ # -f, --param-file PARAMFILE # Load pig parameter values from a file.
35
+ #
36
+ # Examples:
37
+ #
38
+ # $ mortar validate
39
+ #
40
+ # TBD
41
+ #
42
+ def index
43
+ pigscript_name = shift_argument
44
+ unless pigscript_name
45
+ error("Usage: mortar validate PIGSCRIPT\nMust specify PIGSCRIPT.")
46
+ end
47
+ validate_arguments!
48
+ validate_git_based_project!
49
+ pigscript = validate_pigscript!(pigscript_name)
50
+ git_ref = create_and_push_snapshot_branch(git, project)
51
+
52
+ validate_id = nil
53
+ action("Starting validate") do
54
+ validate_id = api.post_validate(project.name, pigscript.name, git_ref, :parameters => pig_parameters).body["validate_id"]
55
+ end
56
+
57
+ validate_result = nil
58
+ display
59
+ ticking(polling_interval) do |ticks|
60
+ validate_result = api.get_validate(validate_id).body
61
+ is_finished =
62
+ Mortar::API::Validate::STATUSES_COMPLETE.include?(validate_result["status_code"])
63
+
64
+ redisplay("Status: %s %s" % [
65
+ validate_result['status_description'] + (is_finished ? "" : "..."),
66
+ is_finished ? " " : spinner(ticks)],
67
+ is_finished) # only display newline on last message
68
+ if is_finished
69
+ display
70
+ break
71
+ end
72
+ end
73
+
74
+ case validate_result['status_code']
75
+ when Mortar::API::Validate::STATUS_FAILURE
76
+ error_message = "Validate failed with #{validate_result['error_type'] || 'error'}"
77
+ if line_number = validate_result["line_number"]
78
+ error_message += " at Line #{line_number}"
79
+ if column_number = validate_result["column_number"]
80
+ error_message += ", Column #{column_number}"
81
+ end
82
+ end
83
+ error_context = get_error_message_context(validate_result['error_message'])
84
+ error_message += ":\n\n#{validate_result['error_message']}\n\n#{error_context}"
85
+ error(error_message)
86
+ when Mortar::API::Validate::STATUS_KILLED
87
+ error("Validate killed by user.")
88
+ when Mortar::API::Validate::STATUS_SUCCESS
89
+ display("Your script is valid.")
90
+ else
91
+ raise RuntimeError, "Unknown validate status: #{validate_result['status']} for validate_id: #{validate_id}"
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,42 @@
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
+ # Portions of this code from heroku (https://github.com/heroku/heroku/) Copyright Heroku 2008 - 2012,
17
+ # used under an MIT license (https://github.com/heroku/heroku/blob/master/LICENSE).
18
+ #
19
+
20
+ require "mortar/command/base"
21
+ require "mortar/version"
22
+
23
+ # display version
24
+ #
25
+ class Mortar::Command::Version < Mortar::Command::Base
26
+
27
+ # version
28
+ #
29
+ # show mortar client version
30
+ #
31
+ #Example:
32
+ #
33
+ # $ mortar version
34
+ # mortar/1.2.3 (x86_64-darwin11.4.2) ruby/1.9.3
35
+ #
36
+ def index
37
+ validate_arguments!
38
+
39
+ display(Mortar::USER_AGENT)
40
+ end
41
+
42
+ end