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,24 @@
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
+ module Mortar
18
+ class CLI
19
+ module Errors
20
+ class InvalidGithubUsername < StandardError; end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,107 @@
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 "erb"
18
+ require "fileutils"
19
+
20
+ module Mortar
21
+ module Generators
22
+ class Base
23
+ include Mortar::Helpers
24
+
25
+ def initialize()
26
+ # This is a really ugly way to turn the subclass name 'Mortar::Generators::ProjectGenerator'
27
+ # into 'project' so we can use it to find the appropriate templates folder
28
+
29
+ generator_type = self.class.name.split("::")[2].match(/.*(?=([A-Z]))/)[0].downcase
30
+ @src_path = File.expand_path("../../templates/#{generator_type}", __FILE__)
31
+ @dest_path = Dir.pwd
32
+ @rel_path = ""
33
+ @binding_variables = {}
34
+ end
35
+
36
+ def inside(folder)
37
+ rel_backup = @rel_path
38
+ @rel_path = File.join(@rel_path, folder)
39
+ yield
40
+ @rel_path = rel_backup
41
+ end
42
+
43
+ def copy_file(src_file, dest_file, options={ :recursive => false })
44
+ src_path = File.join(@src_path, @rel_path, src_file)
45
+ dest_path = File.join(@dest_path, @rel_path, dest_file)
46
+ msg = File.join(@rel_path, dest_file)[1..-1]
47
+
48
+ if File.exists?(dest_path)
49
+ if FileUtils.compare_file(src_path, dest_path)
50
+ display_identical(msg)
51
+ else
52
+ display_conflict(msg)
53
+ end
54
+ else
55
+ display_create(msg)
56
+ FileUtils.mkdir_p(File.dirname(dest_path)) if options[:recursive]
57
+ FileUtils.cp(src_path, dest_path)
58
+ end
59
+ end
60
+
61
+ def mkdir(folder, options={ :verbose => true })
62
+ dest_path = File.join(@dest_path, @rel_path, folder)
63
+ msg = File.join(@rel_path, folder)[1..-1]
64
+
65
+ if File.exists?(dest_path)
66
+ display_exists(options[:verbose] ? msg : "")
67
+ else
68
+ display_create(options[:verbose] ? msg : "")
69
+ FileUtils.mkdir(dest_path)
70
+ end
71
+ end
72
+
73
+ def generate_file(src_file, dest_file, options={ :recursive => false })
74
+ src_path = File.join(@src_path, @rel_path, src_file)
75
+ dest_path = File.join(@dest_path, @rel_path, dest_file)
76
+ msg = File.join(@rel_path, dest_file)[1..-1]
77
+
78
+ erb = ERB.new(File.read(src_path), 0, "%<>")
79
+
80
+ result = erb.result(@script_binding)
81
+
82
+
83
+ if File.exists?(dest_path)
84
+ if result == File.read(dest_path)
85
+ display_identical(msg)
86
+ else
87
+ display_conflict(msg)
88
+ end
89
+ else
90
+ FileUtils.mkdir_p(File.dirname(dest_path)) if options[:recursive]
91
+ file = File.new(dest_path, "w")
92
+ file.write(result)
93
+ file.close
94
+ display_create(msg)
95
+ end
96
+ end
97
+
98
+ protected
99
+
100
+ def set_script_binding(options)
101
+ options = options
102
+ binding
103
+ end
104
+
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,37 @@
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/generators/generator_base"
18
+ module Mortar
19
+ module Generators
20
+ class MacroGenerator < Base
21
+
22
+ def generate_macro(macro_name, project, options)
23
+ set_script_binding(macro_name, options)
24
+ generate_file "macro.pig", "macros/#{macro_name}.pig", :recursive => true
25
+ end
26
+
27
+ protected
28
+
29
+ def set_script_binding(macro_name, options)
30
+ options = options
31
+ macro_name = macro_name
32
+ @script_binding = binding
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,40 @@
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/generators/generator_base"
18
+ module Mortar
19
+ module Generators
20
+ class PigscriptGenerator < Base
21
+
22
+ def generate_pigscript(script_name, project, options)
23
+ set_script_binding(script_name, options)
24
+
25
+
26
+ generate_file "pigscript.pig", "pigscripts/#{script_name}.pig", :recursive => true
27
+ copy_file "python_udf.py", "udfs/python/#{script_name}.py", :recursive => true if not options[:skip_udf]
28
+ end
29
+
30
+ protected
31
+
32
+ def set_script_binding(script_name, options)
33
+ options = options
34
+ script_name = script_name
35
+ @script_binding = binding
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,67 @@
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/generators/generator_base"
18
+ module Mortar
19
+ module Generators
20
+ class ProjectGenerator < Base
21
+
22
+ def generate_project(project_name, options)
23
+
24
+ set_script_binding(project_name, options)
25
+ mkdir project_name, :verbose => false
26
+ @dest_path = File.join(@dest_path, project_name)
27
+
28
+ copy_file "README.md", "README.md"
29
+ copy_file "gitignore", ".gitignore"
30
+ copy_file "Gemfile", "Gemfile"
31
+
32
+ mkdir "pigscripts"
33
+
34
+ inside "pigscripts" do
35
+ generate_file "pigscript.pig", "#{project_name}.pig"
36
+ end
37
+
38
+ mkdir "macros"
39
+
40
+ inside "macros" do
41
+ copy_file "gitkeep", ".gitkeep"
42
+ end
43
+
44
+ mkdir "udfs"
45
+
46
+ inside "udfs" do
47
+ mkdir "python"
48
+ inside "python" do
49
+ copy_file "python_udf.py", "#{project_name}.py"
50
+ end
51
+ end
52
+
53
+ display_run("bundle install")
54
+ `cd #{project_name} && bundle install && cd ..`
55
+
56
+ end
57
+
58
+ protected
59
+
60
+ def set_script_binding(project_name, options)
61
+ options = options
62
+ project_name = project_name
63
+ @script_binding = binding
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,28 @@
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/generators/generator_base"
18
+ module Mortar
19
+ module Generators
20
+ class UDFGenerator < Base
21
+
22
+ def generate_python_udf(udf_name, project, options)
23
+ copy_file "python_udf.py", "udfs/python/#{udf_name}.py", :recursive => true
24
+ end
25
+
26
+ end
27
+ end
28
+ end
data/lib/mortar/git.rb ADDED
@@ -0,0 +1,233 @@
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 "vendor/mortar/uuid"
18
+ require "set"
19
+
20
+ module Mortar
21
+ module Git
22
+
23
+ class GitError < RuntimeError; end
24
+
25
+ class Git
26
+
27
+ #
28
+ # core commands
29
+ #
30
+
31
+ def has_git?
32
+ %x{ git --version }
33
+ $?.success?
34
+ end
35
+
36
+ def has_dot_git?
37
+ File.directory?(".git")
38
+ end
39
+
40
+ def git(args, check_success=true, check_git_directory=true)
41
+ unless has_git?
42
+ raise GitError, "git must be installed"
43
+ end
44
+
45
+ if check_git_directory && !has_dot_git?
46
+ raise GitError, "No .git directory found"
47
+ end
48
+
49
+ flattened_args = [args].flatten.compact.join(" ")
50
+ output = %x{ git #{flattened_args} 2>&1 }.strip
51
+ success = $?.success?
52
+ if check_success && (! success)
53
+ raise GitError, "Error executing 'git #{flattened_args}':\n#{output}"
54
+ end
55
+ output
56
+ end
57
+
58
+ #
59
+ # snapshot
60
+ #
61
+
62
+ def create_snapshot_branch
63
+ # TODO: handle Ctrl-C in the middle
64
+ # TODO: can we do the equivalent of stash without changing the working directory
65
+ unless has_commits?
66
+ raise GitError, "No commits found in repository. You must do an initial commit to initialize the repository."
67
+ end
68
+
69
+ starting_branch = current_branch
70
+ snapshot_branch = "mortar-snapshot-#{Mortar::UUID.create_random.to_s}"
71
+ did_stash_changes = stash_working_dir(snapshot_branch)
72
+ begin
73
+ # checkout a new branch
74
+ git("checkout -b #{snapshot_branch}")
75
+
76
+ if did_stash_changes
77
+ # apply the topmost stash that we just created
78
+ git("stash apply stash@{0}")
79
+ end
80
+
81
+ add_untracked_files()
82
+
83
+ # commit the changes if there are any
84
+ if ! is_clean_working_directory?
85
+ git("commit -a -m \"mortar development snapshot commit\"")
86
+ end
87
+
88
+ ensure
89
+
90
+ # return to the starting branch
91
+ git("checkout #{starting_branch}")
92
+
93
+ # rebuild the original state of the working set
94
+ if did_stash_changes
95
+ git("stash pop stash@{0}")
96
+ end
97
+ end
98
+
99
+ snapshot_branch
100
+ end
101
+
102
+ #
103
+ # add
104
+ #
105
+
106
+ def add(path)
107
+ git("add #{path}")
108
+ end
109
+
110
+ def add_untracked_files
111
+ untracked_files.each do |untracked_file|
112
+ add untracked_file
113
+ end
114
+ end
115
+
116
+ #
117
+ # branch
118
+ #
119
+
120
+ def branches
121
+ git("branch")
122
+ end
123
+
124
+ def current_branch
125
+ branches.split("\n").each do |branch_listing|
126
+
127
+ # current branch will be the one that starts with *, e.g.
128
+ # not_my_current_branch
129
+ # * my_current_branch
130
+ if branch_listing =~ /^\*\s(\S*)/
131
+ return $1
132
+ end
133
+ end
134
+ raise GitError, "Unable to find current branch in list #{branches}"
135
+ end
136
+
137
+ def branch_delete(branch_name)
138
+ git("branch -D #{branch_name}")
139
+ end
140
+
141
+ #
142
+ # push
143
+ #
144
+
145
+ def push(remote_name, ref)
146
+ git("push #{remote_name} #{ref}")
147
+ end
148
+
149
+
150
+ #
151
+ # remotes
152
+ #
153
+
154
+ def remotes(git_organization)
155
+ # returns {git_remote_name => project_name}
156
+ remotes = {}
157
+ git("remote -v").split("\n").each do |remote|
158
+ name, url, method = remote.split(/\s/)
159
+ if url =~ /^git@([\w\d\.]+):#{git_organization}\/[a-zA-Z0-9]+_([\w\d-]+)\.git$$/
160
+ remotes[name] = $2
161
+ end
162
+ end
163
+
164
+ remotes
165
+ end
166
+
167
+ def remote_add(name, url)
168
+ git("remote add #{name} #{url}")
169
+ end
170
+
171
+ #
172
+ # rev-parse
173
+ #
174
+ def git_ref(refname)
175
+ git("rev-parse --verify --quiet #{refname}")
176
+ end
177
+
178
+ #
179
+ # stash
180
+ #
181
+
182
+ def stash_working_dir(stash_description)
183
+ stash_output = git("stash save --include-untracked #{stash_description}")
184
+ did_stash_changes? stash_output
185
+ end
186
+
187
+ def did_stash_changes?(stash_message)
188
+ ! (stash_message.include? "No local changes to save")
189
+ end
190
+
191
+ #
192
+ # status
193
+ #
194
+
195
+ def status
196
+ git('status --porcelain')
197
+ end
198
+
199
+
200
+ def has_commits?
201
+ # see http://stackoverflow.com/a/5492347
202
+ %x{ git rev-parse --verify --quiet HEAD }
203
+ $?.success?
204
+ end
205
+
206
+ def is_clean_working_directory?
207
+ status.empty?
208
+ end
209
+
210
+ # see https://www.kernel.org/pub/software/scm/git/docs/git-status.html#_output
211
+ GIT_STATUS_CODES__CONFLICT = Set.new ["DD", "AU", "UD", "UA", "DU", "AA", "UU"]
212
+ def has_conflicts?
213
+ def status_code(status_str)
214
+ status_str[0,2]
215
+ end
216
+
217
+ status_codes = status.split("\n").collect{|s| status_code(s)}
218
+ ! GIT_STATUS_CODES__CONFLICT.intersection(status_codes).empty?
219
+ end
220
+
221
+ def untracked_files
222
+ git("ls-files -o --exclude-standard").split("\n")
223
+ end
224
+
225
+ #
226
+ # clone
227
+ #
228
+ def clone(git_url, path="")
229
+ git("clone %s \"%s\"" % [git_url, path], true, false)
230
+ end
231
+ end
232
+ end
233
+ end