mortar 0.1.0

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