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,70 @@
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 'spec_helper'
18
+ require 'fakefs/spec_helpers'
19
+ require 'mortar/project'
20
+ require 'mortar/command/pigscripts'
21
+
22
+ module Mortar::Command
23
+ describe PigScripts do
24
+
25
+ # use FakeFS file system
26
+ include FakeFS::SpecHelpers
27
+
28
+ before(:each) do
29
+ stub_core
30
+ end
31
+
32
+ context("index") do
33
+
34
+ it "displays a message when no pigscripts found" do
35
+ with_blank_project do |p|
36
+ stderr, stdout = execute("pigscripts", p)
37
+ stdout.should == <<-STDOUT
38
+ You have no pigscripts.
39
+ STDOUT
40
+ end
41
+ end
42
+
43
+ it "displays list of 1 pigscript" do
44
+ with_blank_project do |p|
45
+ write_file(File.join(p.pigscripts_path, "my_script.pig"))
46
+ stderr, stdout = execute("pigscripts", p)
47
+ stdout.should == <<-STDOUT
48
+ === pigscripts
49
+ my_script
50
+
51
+ STDOUT
52
+ end
53
+ end
54
+
55
+ it "displays list of multiple pigscripts" do
56
+ with_blank_project do |p|
57
+ write_file(File.join(p.pigscripts_path, "a_script.pig"))
58
+ write_file(File.join(p.pigscripts_path, "b_script.pig"))
59
+ stderr, stdout = execute("pigscripts", p)
60
+ stdout.should == <<-STDOUT
61
+ === pigscripts
62
+ a_script
63
+ b_script
64
+
65
+ STDOUT
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,165 @@
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 'spec_helper'
18
+ require 'fakefs/spec_helpers'
19
+ require 'mortar/command/projects'
20
+ require 'launchy'
21
+ require "mortar/api"
22
+
23
+
24
+ module Mortar::Command
25
+ describe Projects do
26
+
27
+ before(:each) do
28
+ stub_core
29
+ @git = Mortar::Git::Git.new
30
+ end
31
+
32
+ project1 = {'name' => "Project1",
33
+ 'status' => Mortar::API::Projects::STATUS_ACTIVE,
34
+ 'git_url' => "git@github.com:mortarcode/Project1"}
35
+ project2 = {'name' => "Project2",
36
+ 'status' => Mortar::API::Projects::STATUS_ACTIVE,
37
+ 'git_url' => "git@github.com:mortarcode/Project2"}
38
+
39
+ context("index") do
40
+
41
+ it "shows appropriate message when user has no projects" do
42
+ mock(Mortar::Auth.api).get_projects().returns(Excon::Response.new(:body => {"projects" => []}))
43
+
44
+ stderr, stdout = execute("projects")
45
+ stdout.should == <<-STDOUT
46
+ You have no projects.
47
+ STDOUT
48
+ end
49
+
50
+ it "shows appropriate message when user has multiple projects" do
51
+ mock(Mortar::Auth.api).get_projects().returns(Excon::Response.new(:body => {"projects" => [project1, project2]}))
52
+
53
+ stderr, stdout = execute("projects")
54
+ stdout.should == <<-STDOUT
55
+ === projects
56
+ Project1
57
+ Project2
58
+
59
+ STDOUT
60
+ end
61
+ end
62
+
63
+ context("create") do
64
+
65
+ it "show appropriate error message when user doesn't include project name" do
66
+ stderr, stdout = execute("projects:create")
67
+ stderr.should == <<-STDERR
68
+ ! Usage: mortar projects:create PROJECT
69
+ ! Must specify PROJECT.
70
+ STDERR
71
+ end
72
+
73
+ it "try to create project in directory that doesn't have a git repository" do
74
+ with_no_git_directory do
75
+ stderr, stdout = execute("projects:create some_new_project")
76
+ stderr.should == <<-STDERR
77
+ ! Can only create a mortar project for an existing git project. Please run:
78
+ !
79
+ ! git init
80
+ ! git add .
81
+ ! git commit -a -m "first commit"
82
+ !
83
+ ! to initialize your project in git.
84
+ STDERR
85
+ end
86
+ end
87
+
88
+ it "show appropriate error message when user tries to create a project inside of an existing project" do
89
+ with_git_initialized_project do |p|
90
+ stderr, stdout = execute("projects:create some_new_project", nil, @git)
91
+ stderr.should == <<-STDERR
92
+ ! Currently in project: myproject. You can not create a new project inside of an existing mortar project.
93
+ STDERR
94
+ end
95
+ end
96
+
97
+ it "create a new project successfully" do
98
+ project_id = "1234abcd1234abcd1234"
99
+ project_name = "some_new_project"
100
+ project_git_url = "git@github.com:mortarcode/#{project_name}"
101
+ mock(Mortar::Auth.api).post_project("some_new_project") {Excon::Response.new(:body => {"project_id" => project_id})}
102
+ mock(Mortar::Auth.api).get_project(project_id).returns(Excon::Response.new(:body => {"status" => Mortar::API::Projects::STATUS_PENDING})).ordered
103
+ mock(Mortar::Auth.api).get_project(project_id).returns(Excon::Response.new(:body => {"status" => Mortar::API::Projects::STATUS_CREATING})).ordered
104
+ mock(Mortar::Auth.api).get_project(project_id).returns(Excon::Response.new(:body => {"status" => Mortar::API::Projects::STATUS_ACTIVE,
105
+ "git_url" => project_git_url})).ordered
106
+ mock(@git).remote_add("mortar", project_git_url)
107
+
108
+ stderr, stdout = execute("projects:create #{project_name} --polling_interval 0.05", nil, @git)
109
+ stdout.should == <<-STDOUT
110
+ Creating project... started
111
+ ... PENDING
112
+ ... CREATING
113
+ ... ACTIVE
114
+ STDOUT
115
+ end
116
+
117
+ end
118
+
119
+
120
+ context("clone") do
121
+
122
+ it "shows appropriate error message when user doesn't include project name" do
123
+ stderr, stdout = execute("projects:clone")
124
+ stderr.should == <<-STDERR
125
+ ! Usage: mortar projects:clone PROJECT
126
+ ! Must specify PROJECT.
127
+ STDERR
128
+ end
129
+
130
+ it "shows appropriate error message when user tries to clone non-existent project" do
131
+ mock(Mortar::Auth.api).get_projects().returns(Excon::Response.new(:body => {"projects" => [project1, project2]}))
132
+
133
+ stderr, stdout = execute('projects:clone sillyProjectName')
134
+ stderr.should == <<-STDERR
135
+ ! No project named: sillyProjectName exists. Your valid projects are:
136
+ ! Project1
137
+ ! Project2
138
+ STDERR
139
+ end
140
+
141
+ it "shows appropriate error message when user tries to clone into existing directory" do
142
+ with_no_git_directory do
143
+ mock(Mortar::Auth.api).get_projects().returns(Excon::Response.new(:body => {"projects" => [project1, project2]}))
144
+ starting_dir = Dir.pwd
145
+ project_dir = File.join(Dir.pwd, project1['name'])
146
+ FileUtils.mkdir_p(project_dir)
147
+
148
+ stderr, stdout = execute("projects:clone #{project1['name']}")
149
+ stderr.should == <<-STDERR
150
+ ! Can't clone project: #{project1['name']} since directory with that name already exists.
151
+ STDERR
152
+ end
153
+
154
+ end
155
+
156
+ it "calls git clone when existing project is cloned" do
157
+ mock(Mortar::Auth.api).get_projects().returns(Excon::Response.new(:body => {"projects" => [project1, project2]}))
158
+ mock(@git).clone(project1['git_url'], project1['name'])
159
+
160
+ stderr, stdout = execute('projects:clone Project1', nil, @git)
161
+ end
162
+
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,119 @@
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 'spec_helper'
18
+ require 'fakefs/spec_helpers'
19
+ require 'mortar/command/validate'
20
+ require 'mortar/api/validate'
21
+ require 'launchy'
22
+
23
+ module Mortar::Command
24
+ describe Validate do
25
+
26
+ before(:each) do
27
+ stub_core
28
+ @git = Mortar::Git::Git.new
29
+ end
30
+
31
+ context("index") do
32
+
33
+ it "errors when no remote exists in the project" do
34
+ with_git_initialized_project do |p|
35
+ @git.git('remote rm mortar')
36
+ p.remote = nil
37
+ write_file(File.join(p.pigscripts_path, "my_script.pig"))
38
+ stderr, stdout = execute("validate my_script", p, @git)
39
+ stderr.should == <<-STDERR
40
+ ! Unable to find git remote for project myproject
41
+ STDERR
42
+ end
43
+ end
44
+
45
+ it "errors when requested pigscript cannot be found" do
46
+ with_git_initialized_project do |p|
47
+ stderr, stdout = execute("validate does_not_exist", p, @git)
48
+ stderr.should == <<-STDERR
49
+ ! Unable to find pigscript does_not_exist
50
+ ! No pigscripts found
51
+ STDERR
52
+ end
53
+ end
54
+
55
+ it "requests and reports on a successful validate" do
56
+ with_git_initialized_project do |p|
57
+ # stub api requests
58
+ validate_id = "c571a8c7f76a4fd4a67c103d753e2dd5"
59
+ parameters = ["name"=>"key", "value"=>"value" ]
60
+
61
+ mock(Mortar::Auth.api).post_validate("myproject", "my_script", is_a(String), :parameters => parameters) {Excon::Response.new(:body => {"validate_id" => validate_id})}
62
+ mock(Mortar::Auth.api).get_validate(validate_id).returns(Excon::Response.new(:body => {"status_code" => Mortar::API::Validate::STATUS_QUEUED, "status_description" => "Pending"})).ordered
63
+ mock(Mortar::Auth.api).get_validate(validate_id).returns(Excon::Response.new(:body => {"status_code" => Mortar::API::Validate::STATUS_GATEWAY_STARTING, "status_description" => "GATEWAY_STARTING"})).ordered
64
+ mock(Mortar::Auth.api).get_validate(validate_id).returns(Excon::Response.new(:body => {"status_code" => Mortar::API::Validate::STATUS_PROGRESS, "status_description" => "Starting"})).ordered
65
+ mock(Mortar::Auth.api).get_validate(validate_id).returns(Excon::Response.new(:body => {"status_code" => Mortar::API::Validate::STATUS_SUCCESS, "status_description" => "Success"})).ordered
66
+
67
+ write_file(File.join(p.pigscripts_path, "my_script.pig"))
68
+ stderr, stdout = execute("validate my_script --polling_interval 0.05 -p key=value", p, @git)
69
+ stdout.should == <<-STDOUT
70
+ Taking code snapshot... done
71
+ Sending code snapshot to Mortar... done
72
+ Starting validate... done
73
+
74
+ \r\e[0KStatus: Pending... /\r\e[0KStatus: GATEWAY_STARTING... -\r\e[0KStatus: Starting... \\\r\e[0KStatus: Success
75
+
76
+ Your script is valid.
77
+ STDOUT
78
+ end
79
+ end
80
+
81
+ it "requests and reports on a failed validate" do
82
+ with_git_initialized_project do |p|
83
+ # stub api requests
84
+ validate_id = "c571a8c7f76a4fd4a67c103d753e2dd5"
85
+
86
+ error_message = "This is my error message\nWith multiple lines."
87
+ line_number = 23
88
+ column_number = 32
89
+ error_type = 'PigError'
90
+
91
+ mock(Mortar::Auth.api).post_validate("myproject", "my_script", is_a(String), :parameters => []) {Excon::Response.new(:body => {"validate_id" => validate_id})}
92
+ mock(Mortar::Auth.api).get_validate(validate_id).returns(Excon::Response.new(:body => {"status_code" => Mortar::API::Validate::STATUS_QUEUED, "status_description" => "Pending"})).ordered
93
+ mock(Mortar::Auth.api).get_validate(validate_id).returns(Excon::Response.new(:body => {"status_code" => Mortar::API::Validate::STATUS_FAILURE, "status_description" => "Failed",
94
+ "error_message" => error_message,
95
+ "line_number" => line_number,
96
+ "column_number" => column_number,
97
+ "error_type" => error_type})).ordered
98
+
99
+ write_file(File.join(p.pigscripts_path, "my_script.pig"))
100
+ stderr, stdout = execute("validate my_script --polling_interval 0.05", p, @git)
101
+ stdout.should == <<-STDOUT
102
+ Taking code snapshot... done
103
+ Sending code snapshot to Mortar... done
104
+ Starting validate... done
105
+
106
+ \r\e[0KStatus: Pending... /\r\e[0KStatus: Failed
107
+
108
+ STDOUT
109
+ stderr.should == <<-STDERR
110
+ ! Validate failed with PigError at Line 23, Column 32:
111
+ !
112
+ ! This is my error message
113
+ ! With multiple lines.
114
+ STDERR
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,122 @@
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 "spec_helper"
21
+ require "mortar/command"
22
+
23
+ class FakeResponse
24
+
25
+ attr_accessor :body, :headers
26
+
27
+ def initialize(attributes)
28
+ self.body, self.headers = attributes[:body], attributes[:headers]
29
+ end
30
+
31
+ def to_s
32
+ body
33
+ end
34
+
35
+ end
36
+
37
+ describe Mortar::Command do
38
+ before {
39
+ Mortar::Command.load
40
+ stub_core # setup fake auth
41
+ }
42
+
43
+ describe "parsing errors" do
44
+ it "extracts error messages from response when available in XML" do
45
+ Mortar::Command.extract_error('<errors><error>Invalid app name</error></errors>').should == 'Invalid app name'
46
+ end
47
+
48
+ it "extracts error messages from response when available in JSON" do
49
+ Mortar::Command.extract_error("{\"error\":\"Invalid app name\"}").should == 'Invalid app name'
50
+ end
51
+
52
+ it "extracts error messages from response when available in plain text" do
53
+ response = FakeResponse.new(:body => "Invalid app name", :headers => { :content_type => "text/plain; charset=UTF8" })
54
+ Mortar::Command.extract_error(response).should == 'Invalid app name'
55
+ end
56
+
57
+ it "shows Internal Server Error when the response doesn't contain a XML or JSON" do
58
+ Mortar::Command.extract_error('<h1>HTTP 500</h1>').should == "Internal server error."
59
+ end
60
+
61
+ it "shows Internal Server Error when the response is not plain text" do
62
+ response = FakeResponse.new(:body => "Foobar", :headers => { :content_type => "application/xml" })
63
+ Mortar::Command.extract_error(response).should == "Internal server error."
64
+ end
65
+
66
+ it "allows a block to redefine the default error" do
67
+ Mortar::Command.extract_error("Foobar") { "Ok!" }.should == 'Ok!'
68
+ end
69
+
70
+ it "doesn't format the response if set to raw" do
71
+ Mortar::Command.extract_error("Foobar", :raw => true) { "Ok!" }.should == 'Ok!'
72
+ end
73
+
74
+ it "handles a nil body in parse_error_xml" do
75
+ lambda { Mortar::Command.parse_error_xml(nil) }.should_not raise_error
76
+ end
77
+
78
+ it "handles a nil body in parse_error_json" do
79
+ lambda { Mortar::Command.parse_error_json(nil) }.should_not raise_error
80
+ end
81
+ end
82
+
83
+ it "correctly resolves commands" do
84
+ class Mortar::Command::Test; end
85
+ class Mortar::Command::Test::Multiple; end
86
+
87
+ require "mortar/command/help"
88
+ require "mortar/command/illustrate"
89
+
90
+ Mortar::Command.parse("unknown").should be_nil
91
+ Mortar::Command.parse("help").should include(:klass => Mortar::Command::Help, :method => :index)
92
+ Mortar::Command.parse("illustrate").should include(:klass => Mortar::Command::Illustrate, :method => :index)
93
+ end
94
+
95
+ context "when no commands match" do
96
+
97
+ it "displays the version if -v or --version is used" do
98
+ mortar("-v").should == <<-STDOUT
99
+ #{Mortar::USER_AGENT}
100
+ STDOUT
101
+ mortar("--version").should == <<-STDOUT
102
+ #{Mortar::USER_AGENT}
103
+ STDOUT
104
+ end
105
+
106
+ it "does not suggest similar commands if there are none" do
107
+ original_stderr, original_stdout = $stderr, $stdout
108
+ $stderr = captured_stderr = StringIO.new
109
+ $stdout = captured_stdout = StringIO.new
110
+ begin
111
+ execute("sandwich")
112
+ rescue SystemExit
113
+ end
114
+ captured_stderr.string.should == <<-STDERR
115
+ ! `sandwich` is not a mortar command.
116
+ ! See `mortar help` for a list of available commands.
117
+ STDERR
118
+ captured_stdout.string.should == ""
119
+ $stderr, $stdout = original_stderr, original_stdout
120
+ end
121
+ end
122
+ end