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,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